X-Git-Url: https://gitweb.dragonflybsd.org/~tuxillo/dragonfly.git/blobdiff_plain/6b445a628d016cb98daa614ade67aed0ed1d4ec0..ef5ccd6c41237a870dd7242b72b006d6bd42cd07:/contrib/gdb-7/gdb/record.c diff --git a/contrib/gdb-7/gdb/record.c b/contrib/gdb-7/gdb/record.c index e396262a52..6bc1704a25 100644 --- a/contrib/gdb-7/gdb/record.c +++ b/contrib/gdb-7/gdb/record.c @@ -1,6 +1,6 @@ /* Process record and replay target for GDB, the GNU debugger. - Copyright (C) 2008-2012 Free Software Foundation, Inc. + Copyright (C) 2008-2013 Free Software Foundation, Inc. This file is part of GDB. @@ -19,2092 +19,160 @@ #include "defs.h" #include "gdbcmd.h" -#include "regcache.h" -#include "gdbthread.h" -#include "event-top.h" -#include "exceptions.h" #include "completer.h" -#include "arch-utils.h" -#include "gdbcore.h" -#include "exec.h" #include "record.h" -#include "elf-bfd.h" -#include "gcore.h" -#include "event-loop.h" -#include "inf-loop.h" +#include "observer.h" +#include "inferior.h" +#include "common/common-utils.h" +#include "cli/cli-utils.h" +#include "disasm.h" -#include +#include -/* This module implements "target record", also known as "process - record and replay". This target sits on top of a "normal" target - (a target that "has execution"), and provides a record and replay - functionality, including reverse debugging. - - 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; -}; - -/* 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 *); -static void (*record_beneath_to_async) (void (*) (enum inferior_event_type, void *), void *); - -/* Alloc and free functions for record_reg, record_mem, and record_end - entries. */ - -/* Alloc a record_reg record entry. */ - -static inline struct record_entry * -record_reg_alloc (struct regcache *regcache, int regnum) -{ - 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); -} - -/* Alloc a record_mem record entry. */ - -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; -} - -/* Free a record_end record entry. */ - -static inline void -record_end_release (struct record_entry *rec) -{ - xfree (rec); -} - -/* Free one record entry, any type. - Return entry->type, in case caller wants to know. */ - -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; -} - -/* Free all record entries in list pointed to by REC. */ - -static void -record_list_release (struct record_entry *rec) -{ - 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); -} - -/* Free all record entries forward of the given list position. */ - -static void -record_list_release_following (struct record_entry *rec) -{ - 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_not_reached ("unexpected record_entry type"); - 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)); - - record_arch_list_add (rec); - - return 0; -} - -/* Record the value of a region of memory whose address is ADDR and - length is LEN to record_arch_list. */ - -int -record_arch_list_add_mem (CORE_ADDR addr, int len) -{ - 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; -} - -/* Add a record_end type struct record_entry to record_arch_list. */ - -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) -{ - struct cleanup *old_cleanups = NULL; - - old_cleanups = - make_cleanup_restore_integer (&record_gdb_operation_disable); - record_gdb_operation_disable = 1; - - 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 int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *); -static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *); - -static void record_restore (void); - -/* Asynchronous signal handle registered as event loop source for when - we have pending events ready to be passed to the core. */ - -static struct async_event_handler *record_async_inferior_event_token; - -static void -record_async_inferior_event_handler (gdb_client_data data) -{ - inferior_event_handler (INF_REG_EVENT, NULL); -} - -/* 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 (!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; - tmp_to_async = NULL; - - /* Set the beneath function pointers. */ - for (t = current_target.beneath; t != NULL; t = t->beneath) - { - if (!tmp_to_resume) - { - tmp_to_resume = t->to_resume; - tmp_to_resume_ops = t; - } - if (!tmp_to_wait) - { - tmp_to_wait = t->to_wait; - tmp_to_wait_ops = t; - } - if (!tmp_to_store_registers) - { - tmp_to_store_registers = t->to_store_registers; - tmp_to_store_registers_ops = t; - } - if (!tmp_to_xfer_partial) - { - tmp_to_xfer_partial = t->to_xfer_partial; - tmp_to_xfer_partial_ops = t; - } - if (!tmp_to_insert_breakpoint) - tmp_to_insert_breakpoint = t->to_insert_breakpoint; - if (!tmp_to_remove_breakpoint) - tmp_to_remove_breakpoint = t->to_remove_breakpoint; - if (!tmp_to_stopped_by_watchpoint) - tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint; - if (!tmp_to_stopped_data_address) - tmp_to_stopped_data_address = t->to_stopped_data_address; - if (!tmp_to_async) - tmp_to_async = t->to_async; - } - if (!tmp_to_xfer_partial) - error (_("Could not find 'to_xfer_partial' method on the target stack.")); - - /* Reset */ - record_insn_num = 0; - record_insn_count = 0; - record_list = &record_first; - record_list->next = NULL; - - /* Set the tmp beneath pointers to beneath pointers. */ - record_beneath_to_resume_ops = tmp_to_resume_ops; - record_beneath_to_resume = tmp_to_resume; - record_beneath_to_wait_ops = tmp_to_wait_ops; - record_beneath_to_wait = tmp_to_wait; - record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; - record_beneath_to_store_registers = tmp_to_store_registers; - record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; - record_beneath_to_xfer_partial = tmp_to_xfer_partial; - record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; - record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; - record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint; - record_beneath_to_stopped_data_address = tmp_to_stopped_data_address; - record_beneath_to_async = tmp_to_async; - - if (core_bfd) - record_core_open_1 (name, from_tty); - else - record_open_1 (name, from_tty); - - /* Register extra event sources in the event loop. */ - record_async_inferior_event_token - = create_async_event_handler (record_async_inferior_event_handler, - NULL); -} - -/* "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; - } - - if (record_async_inferior_event_token) - delete_async_event_handler (&record_async_inferior_event_token); -} - -static int record_resume_step = 0; - -/* True if we've been resumed, and so each record_wait call should - advance execution. If this is false, record_wait will return a - TARGET_WAITKIND_IGNORE. */ -static int record_resumed = 0; - -/* The execution direction of the last resume we got. This is - necessary for async mode. Vis (order is not strictly accurate): - - 1. user has the global execution direction set to forward - 2. user does a reverse-step command - 3. record_resume is called with global execution direction - temporarily switched to reverse - 4. GDB's execution direction is reverted back to forward - 5. target record notifies event loop there's an event to handle - 6. infrun asks the target which direction was it going, and switches - the global execution direction accordingly (to reverse) - 7. infrun polls an event out of the record target, and handles it - 8. GDB goes back to the event loop, and goto #4. -*/ -static enum exec_direction_kind record_execution_dir = EXEC_FORWARD; - -/* "to_resume" target method. Resume the process record target. */ - -static void -record_resume (struct target_ops *ops, ptid_t ptid, int step, - enum target_signal signal) -{ - record_resume_step = step; - record_resumed = 1; - record_execution_dir = execution_direction; - - if (!RECORD_IS_REPLAY) - { - struct gdbarch *gdbarch = target_thread_architecture (ptid); - - record_message (get_current_regcache (), signal); - - if (!step) - { - /* This is not hard single step. */ - if (!gdbarch_software_single_step_p (gdbarch)) - { - /* This is a normal continue. */ - step = 1; - } - else - { - /* This arch support soft sigle step. */ - if (single_step_breakpoints_inserted ()) - { - /* This is a soft single step. */ - record_resume_step = 1; - } - else - { - /* This is a continue. - Try to insert a soft single step breakpoint. */ - if (!gdbarch_software_single_step (gdbarch, - get_current_frame ())) - { - /* This system don't want use soft single step. - Use hard sigle step. */ - step = 1; - } - } - } - } - - record_beneath_to_resume (record_beneath_to_resume_ops, - ptid, step, signal); - } - - /* We are about to start executing the inferior (or simulate it), - let's register it with the event loop. */ - if (target_can_async_p ()) - { - target_async (inferior_event_handler, 0); - /* Notify the event loop there's an event to wait for. We do - most of the work in record_wait. */ - mark_async_event_handler (record_async_inferior_event_token); - } -} - -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; -} - -static void -record_wait_cleanups (void *ignore) -{ - 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_1 (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, record_resumed = %d, direction=%s\n", - record_resume_step, record_resumed, - record_execution_dir == EXEC_FORWARD ? "forward" : "reverse"); - - if (!record_resumed) - { - gdb_assert ((options & TARGET_WNOHANG) != 0); - - /* No interesting event. */ - status->kind = TARGET_WAITKIND_IGNORE; - return minus_one_ptid; - } - - record_get_sig = 0; - signal (SIGINT, record_sig_handler); - - if (!RECORD_IS_REPLAY && ops != &record_core_ops) - { - if (record_resume_step) - { - /* This is a single step. */ - return record_beneath_to_wait (record_beneath_to_wait_ops, - ptid, status, options); - } - else - { - /* This is not a single step. */ - ptid_t ret; - CORE_ADDR tmp_pc; - struct gdbarch *gdbarch = target_thread_architecture (inferior_ptid); - - while (1) - { - ret = record_beneath_to_wait (record_beneath_to_wait_ops, - ptid, status, options); - if (status->kind == TARGET_WAITKIND_IGNORE) - { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_wait " - "target beneath not done yet\n"); - return ret; - } - - if (single_step_breakpoints_inserted ()) - remove_single_step_breakpoints (); - - if (record_resume_step) - return ret; - - /* Is this a SIGTRAP? */ - if (status->kind == TARGET_WAITKIND_STOPPED - && status->value.sig == 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 is a single-step trap. Record the - insn and issue another step. - FIXME: this part can be a random SIGTRAP too. - But GDB cannot handle it. */ - int step = 1; - - if (!record_message_wrapper_safe (regcache, - TARGET_SIGNAL_0)) - { - status->kind = TARGET_WAITKIND_STOPPED; - status->value.sig = TARGET_SIGNAL_0; - break; - } - - if (gdbarch_software_single_step_p (gdbarch)) - { - /* Try to insert the software single step breakpoint. - If insert success, set step to 0. */ - set_executing (inferior_ptid, 0); - reinit_frame_cache (); - if (gdbarch_software_single_step (gdbarch, - get_current_frame ())) - step = 0; - set_executing (inferior_ptid, 1); - } - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_wait " - "issuing one more step in the target beneath\n"); - record_beneath_to_resume (record_beneath_to_resume_ops, - ptid, step, - 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; - - discard_cleanups (old_cleanups); - } - - signal (SIGINT, handle_sigint); - - do_cleanups (set_cleanups); - return inferior_ptid; -} - -static ptid_t -record_wait (struct target_ops *ops, - ptid_t ptid, struct target_waitstatus *status, - int options) -{ - ptid_t return_ptid; - - return_ptid = record_wait_1 (ops, ptid, status, options); - if (status->kind != TARGET_WAITKIND_IGNORE) - { - /* We're reporting a stop. Make sure any spurious - target_wait(WNOHANG) doesn't advance the target until the - core wants us resumed again. */ - record_resumed = 0; - } - return return_ptid; -} - -static int -record_stopped_by_watchpoint (void) -{ - if (RECORD_IS_REPLAY) - return record_hw_watchpoint; - else - return record_beneath_to_stopped_by_watchpoint (); -} - -static int -record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p) -{ - if (RECORD_IS_REPLAY) - return 0; - else - return record_beneath_to_stopped_data_address (ops, addr_p); -} - -/* "to_disconnect" method for process record target. */ - -static void -record_disconnect (struct target_ops *target, char *args, int from_tty) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); - - unpush_target (&record_ops); - target_disconnect (args, from_tty); -} - -/* "to_detach" method for process record target. */ - -static void -record_detach (struct target_ops *ops, char *args, int from_tty) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); - - unpush_target (&record_ops); - target_detach (args, from_tty); -} - -/* "to_mourn_inferior" method for process record target. */ - -static void -record_mourn_inferior (struct target_ops *ops) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: " - "record_mourn_inferior\n"); - - unpush_target (&record_ops); - target_mourn_inferior (); -} - -/* Close process record target before killing the inferior process. */ - -static void -record_kill (struct target_ops *ops) -{ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); - - unpush_target (&record_ops); - target_kill (); -} - -/* Record registers change (by user or by GDB) to list as an instruction. */ - -static void -record_registers_change (struct regcache *regcache, int regnum) -{ - /* Check record_insn_num. */ - record_check_insn_num (0); - - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - - if (regnum < 0) - { - int i; - - for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) - { - if (record_arch_list_add_reg (regcache, i)) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - } - } - else - { - if (record_arch_list_add_reg (regcache, regnum)) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - } - if (record_arch_list_add_end ()) - { - record_list_release (record_arch_list_tail); - error (_("Process record: failed to record execution log.")); - } - record_list->next = record_arch_list_head; - record_arch_list_head->prev = record_list; - record_list = record_arch_list_tail; - - if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); - else - record_insn_num++; -} - -/* "to_store_registers" method for process record target. */ - -static void -record_store_registers (struct target_ops *ops, struct regcache *regcache, - int regno) -{ - if (!record_gdb_operation_disable) - { - if (RECORD_IS_REPLAY) - { - int n; - - /* Let user choose if he wants to write register or not. */ - if (regno < 0) - n = - query (_("Because GDB is in replay mode, changing the " - "value of a register will make the execution " - "log unusable from this point onward. " - "Change all registers?")); - else - n = - query (_("Because GDB is in replay mode, changing the value " - "of a register will make the execution log unusable " - "from this point onward. Change register %s?"), - gdbarch_register_name (get_regcache_arch (regcache), - regno)); - - if (!n) - { - /* Invalidate the value of regcache that was set in function - "regcache_raw_write". */ - if (regno < 0) - { - int i; - - for (i = 0; - i < gdbarch_num_regs (get_regcache_arch (regcache)); - i++) - regcache_invalidate (regcache, i); - } - else - regcache_invalidate (regcache, regno); - - error (_("Process record canceled the operation.")); - } - - /* Destroy the record from here forward. */ - record_list_release_following (record_list); - } - - record_registers_change (regcache, regno); - } - record_beneath_to_store_registers (record_beneath_to_store_registers_ops, - regcache, regno); -} - -/* "to_xfer_partial" method. Behavior is conditional on RECORD_IS_REPLAY. - In replay mode, we cannot write memory unles we are willing to - invalidate the record/replay log from this point forward. */ - -static LONGEST -record_xfer_partial (struct target_ops *ops, enum target_object object, - const char *annex, gdb_byte *readbuf, - const gdb_byte *writebuf, ULONGEST offset, LONGEST len) -{ - if (!record_gdb_operation_disable - && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) - { - if (RECORD_IS_REPLAY) - { - /* Let user choose if he wants to write memory or not. */ - if (!query (_("Because GDB is in replay mode, writing to memory " - "will make the execution log unusable from this " - "point onward. Write memory at address %s?"), - paddress (target_gdbarch, offset))) - error (_("Process record canceled the operation.")); - - /* Destroy the record from here forward. */ - record_list_release_following (record_list); - } - - /* Check record_insn_num */ - record_check_insn_num (0); - - /* Record registers change to list as an instruction. */ - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - if (record_arch_list_add_mem (offset, len)) - { - record_list_release (record_arch_list_tail); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: failed to record " - "execution log."); - return -1; - } - if (record_arch_list_add_end ()) - { - record_list_release (record_arch_list_tail); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - "Process record: failed to record " - "execution log."); - return -1; - } - record_list->next = record_arch_list_head; - record_arch_list_head->prev = record_list; - record_list = record_arch_list_tail; - - if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); - else - record_insn_num++; - } - - return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, - object, annex, readbuf, writebuf, - offset, len); -} - -/* 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. */ +/* This is the debug switch for process record. */ +unsigned int record_debug = 0; -static gdb_byte * -record_get_bookmark (char *args, int from_tty) -{ - gdb_byte *ret = NULL; +/* The number of instructions to print in "record instruction-history". */ +static unsigned int record_insn_history_size = 10; - /* Return stringified form of instruction count. */ - if (record_list && record_list->type == record_end) - ret = xstrdup (pulongest (record_list->u.end.insn_num)); +/* The number of functions to print in "record function-call-history". */ +static unsigned int record_call_history_size = 10; - 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; -} +struct cmd_list_element *record_cmdlist = NULL; +struct cmd_list_element *set_record_cmdlist = NULL; +struct cmd_list_element *show_record_cmdlist = NULL; +struct cmd_list_element *info_record_cmdlist = NULL; -/* The implementation of the command "record goto". */ -static void cmd_record_goto (char *, int); +#define DEBUG(msg, args...) \ + if (record_debug) \ + fprintf_unfiltered (gdb_stdlog, "record: " msg "\n", ##args) -/* "to_goto_bookmark" method for process record and prec over core. */ +/* Find the record target in the target stack. */ -static void -record_goto_bookmark (gdb_byte *bookmark, int from_tty) +static struct target_ops * +find_record_target (void) { - 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. */ - } + struct target_ops *t; - cmd_record_goto ((char *) bookmark, from_tty); - return; -} + for (t = current_target.beneath; t != NULL; t = t->beneath) + if (t->to_stratum == record_stratum) + return t; -static void -record_async (void (*callback) (enum inferior_event_type event_type, - void *context), void *context) -{ - /* If we're on top of a line target (e.g., linux-nat, remote), then - set it to async mode as well. Will be NULL if we're sitting on - top of the core target, for "record restore". */ - if (record_beneath_to_async != NULL) - record_beneath_to_async (callback, context); + return NULL; } -static int -record_can_async_p (void) -{ - /* We only enable async when the user specifically asks for it. */ - return target_async_permitted; -} +/* Check that recording is active. Throw an error, if it isn't. */ -static int -record_is_async_p (void) +static struct target_ops * +require_record_target (void) { - /* We only enable async when the user specifically asks for it. */ - return target_async_permitted; -} + struct target_ops *t; -static enum exec_direction_kind -record_execution_direction (void) -{ - return record_execution_dir; -} + t = find_record_target (); + if (t == NULL) + error (_("No record target is currently active.\n" + "Use one of the \"target record-\" commands first.")); -static void -init_record_ops (void) -{ - record_ops.to_shortname = "record"; - record_ops.to_longname = "Process record and replay target"; - record_ops.to_doc = - "Log program while executing and replay execution from log."; - record_ops.to_open = record_open; - record_ops.to_close = record_close; - record_ops.to_resume = record_resume; - record_ops.to_wait = record_wait; - record_ops.to_disconnect = record_disconnect; - record_ops.to_detach = record_detach; - record_ops.to_mourn_inferior = record_mourn_inferior; - record_ops.to_kill = record_kill; - record_ops.to_create_inferior = find_default_create_inferior; - record_ops.to_store_registers = record_store_registers; - record_ops.to_xfer_partial = record_xfer_partial; - record_ops.to_insert_breakpoint = record_insert_breakpoint; - record_ops.to_remove_breakpoint = record_remove_breakpoint; - record_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint; - record_ops.to_stopped_data_address = record_stopped_data_address; - record_ops.to_can_execute_reverse = record_can_execute_reverse; - record_ops.to_stratum = record_stratum; - /* Add bookmark target methods. */ - record_ops.to_get_bookmark = record_get_bookmark; - record_ops.to_goto_bookmark = record_goto_bookmark; - record_ops.to_async = record_async; - record_ops.to_can_async_p = record_can_async_p; - record_ops.to_is_async_p = record_is_async_p; - record_ops.to_execution_direction = record_execution_direction; - record_ops.to_magic = OPS_MAGIC; + return t; } -/* "to_resume" method for prec over corefile. */ +/* See record.h. */ -static void -record_core_resume (struct target_ops *ops, ptid_t ptid, int step, - enum target_signal signal) +int +record_read_memory (struct gdbarch *gdbarch, + CORE_ADDR memaddr, gdb_byte *myaddr, + ssize_t len) { - record_resume_step = step; - record_resumed = 1; - record_execution_dir = execution_direction; + int ret = target_read_memory (memaddr, myaddr, len); - /* We are about to start executing the inferior (or simulate it), - let's register it with the event loop. */ - if (target_can_async_p ()) - { - target_async (inferior_event_handler, 0); + if (ret != 0) + DEBUG ("error reading memory at addr %s len = %ld.\n", + paddress (gdbarch, memaddr), (long) len); - /* Notify the event loop there's an event to wait for. */ - mark_async_event_handler (record_async_inferior_event_token); - } + return ret; } -/* "to_kill" method for prec over corefile. */ +/* Stop recording. */ static void -record_core_kill (struct target_ops *ops) +record_stop (struct target_ops *t) { - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); + DEBUG ("stop %s", t->to_shortname); - unpush_target (&record_core_ops); + if (t->to_stop_recording != NULL) + t->to_stop_recording (); } -/* "to_fetch_registers" method for prec over corefile. */ +/* Unpush the record target. */ static void -record_core_fetch_registers (struct target_ops *ops, - struct regcache *regcache, - int regno) +record_unpush (struct target_ops *t) { - if (regno < 0) - { - int num = gdbarch_num_regs (get_regcache_arch (regcache)); - int i; + DEBUG ("unpush %s", t->to_shortname); - 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); + unpush_target (t); } -/* "to_prepare_to_store" method for prec over corefile. */ +/* See record.h. */ -static void -record_core_prepare_to_store (struct regcache *regcache) +void +record_disconnect (struct target_ops *t, char *args, int from_tty) { -} + gdb_assert (t->to_stratum == record_stratum); -/* "to_store_registers" method for prec over corefile. */ + DEBUG ("disconnect %s", t->to_shortname); -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.")); + record_stop (t); + record_unpush (t); + + target_disconnect (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_detach (struct target_ops *t, char *args, int from_tty) { - if (object == TARGET_OBJECT_MEMORY) - { - if (record_gdb_operation_disable || !writebuf) - { - struct target_section *p; + gdb_assert (t->to_stratum == record_stratum); - 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; - } - } + DEBUG ("detach %s", t->to_shortname); - return -1; - } - else - error (_("You can't do that without a process to debug.")); - } + record_stop (t); + record_unpush (t); - return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, - object, annex, readbuf, writebuf, - offset, len); + target_detach (args, from_tty); } -/* "to_insert_breakpoint" method for prec over corefile. */ +/* See record.h. */ -static int -record_core_insert_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) +void +record_mourn_inferior (struct target_ops *t) { - return 0; -} + gdb_assert (t->to_stratum == record_stratum); -/* "to_remove_breakpoint" method for prec over corefile. */ + DEBUG ("mourn inferior %s", t->to_shortname); -static int -record_core_remove_breakpoint (struct gdbarch *gdbarch, - struct bp_target_info *bp_tgt) -{ - return 0; + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); + + target_mourn_inferior (); } -/* "to_has_execution" method for prec over corefile. */ +/* See record.h. */ -static int -record_core_has_execution (struct target_ops *ops, ptid_t the_ptid) +void +record_kill (struct target_ops *t) { - return 1; -} + gdb_assert (t->to_stratum == record_stratum); -static void -init_record_core_ops (void) -{ - record_core_ops.to_shortname = "record-core"; - record_core_ops.to_longname = "Process record and replay target"; - record_core_ops.to_doc = - "Log program while executing and replay execution from log."; - record_core_ops.to_open = record_open; - record_core_ops.to_close = record_close; - record_core_ops.to_resume = record_core_resume; - record_core_ops.to_wait = record_wait; - record_core_ops.to_kill = record_core_kill; - record_core_ops.to_fetch_registers = record_core_fetch_registers; - record_core_ops.to_prepare_to_store = record_core_prepare_to_store; - record_core_ops.to_store_registers = record_core_store_registers; - record_core_ops.to_xfer_partial = record_core_xfer_partial; - record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; - record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; - record_core_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint; - record_core_ops.to_stopped_data_address = record_stopped_data_address; - record_core_ops.to_can_execute_reverse = record_can_execute_reverse; - record_core_ops.to_has_execution = record_core_has_execution; - record_core_ops.to_stratum = record_stratum; - /* Add bookmark target methods. */ - record_core_ops.to_get_bookmark = record_get_bookmark; - record_core_ops.to_goto_bookmark = record_goto_bookmark; - record_core_ops.to_async = record_async; - record_core_ops.to_can_async_p = record_can_async_p; - record_core_ops.to_is_async_p = record_is_async_p; - record_core_ops.to_execution_direction = record_execution_direction; - record_core_ops.to_magic = OPS_MAGIC; + DEBUG ("kill %s", t->to_shortname); + + /* It is safer to not stop recording. Resources will be freed when + threads are discarded. */ + record_unpush (t); + + target_kill (); } /* Implement "show record debug" command. */ @@ -2122,7 +190,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 @@ -2131,21 +199,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. */ @@ -2153,34 +225,20 @@ 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) @@ -2190,703 +248,407 @@ set_record_command (char *args, int from_tty) 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); + if (t->to_info_record != NULL) + t->to_info_record (); +} - /* 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", PIDGET (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) +/* "record goto" command. Argument is an instruction number, + as given by "info record". + + Rewinds the recording (forward or backward) to the given instruction. */ + +void +cmd_record_goto (char *arg, int from_tty) { - int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len); + require_record_target (); - if (ret) - *offset += len; + 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) + target_goto_record_begin (); + else if (strncmp (arg, "end", strlen ("end")) == 0) + target_goto_record_end (); else - error (_("Failed to read %d bytes from core file %s ('%s')."), - len, bfd_get_filename (obfd), - bfd_errmsg (bfd_get_error ())); + { + ULONGEST insn; + + insn = parse_and_eval_long (arg); + target_goto_record (insn); + } } -static inline uint64_t -netorder64 (uint64_t input) +/* Read an instruction number from an argument string. */ + +static ULONGEST +get_insn_number (char **arg) { - uint64_t ret; + ULONGEST number; + const char *begin, *end, *pos; - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; -} + begin = *arg; + pos = skip_spaces_const (begin); -static inline uint32_t -netorder32 (uint32_t input) -{ - uint32_t ret; + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; + number = strtoulst (pos, &end, 10); + + *arg += (end - begin); + + return number; } -static inline uint16_t -netorder16 (uint16_t input) +/* Read a context size from an argument string. */ + +static int +get_context_size (char **arg) { - uint16_t ret; + char *pos; + int number; - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), - BFD_ENDIAN_BIG, input); - return ret; + pos = skip_spaces (*arg); + + if (!isdigit (*pos)) + error (_("Expected positive number, got: %s."), pos); + + return strtol (pos, arg, 10); } -/* Restore the execution log from a core_bfd file. */ +/* Complain about junk at the end of an argument string. */ + static void -record_restore (void) +no_chunk (char *arg) { - uint32_t magic; - struct cleanup *old_cleanups; - struct record_entry *rec; - asection *osec; - uint32_t osec_size; - int bfd_offset = 0; - struct regcache *regcache; - - /* We restore the execution log from the open core bfd, - if there is one. */ - if (core_bfd == NULL) - return; - - /* "record_restore" can only be called when record list is empty. */ - gdb_assert (record_first.next == NULL); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Restoring recording from core file.\n"); - - /* Now need to find our special note section. */ - osec = bfd_get_section_by_name (core_bfd, "null0"); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n", - osec ? "succeeded" : "failed"); - if (osec == NULL) - return; - osec_size = bfd_section_size (core_bfd, osec); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec)); - - /* Check the magic code. */ - bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset); - if (magic != RECORD_FILE_MAGIC) - error (_("Version mis-match or file format error in core file %s."), - bfd_get_filename (core_bfd)); - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Reading 4-byte magic cookie " - "RECORD_FILE_MAGIC (0x%s)\n", - phex_nz (netorder32 (magic), 4)); - - /* Restore the entries in recfd into record_arch_list_head and - record_arch_list_tail. */ - record_arch_list_head = NULL; - record_arch_list_tail = NULL; - record_insn_num = 0; - old_cleanups = make_cleanup (record_arch_list_cleanups, 0); - regcache = get_current_regcache (); - - while (1) - { - uint8_t rectype; - uint32_t regnum, len, signal, count; - uint64_t addr; - - /* We are finished when offset reaches osec_size. */ - if (bfd_offset >= osec_size) - break; - bfdcore_read (core_bfd, osec, &rectype, sizeof (rectype), &bfd_offset); - - switch (rectype) - { - case record_reg: /* reg */ - /* Get register number to regnum. */ - bfdcore_read (core_bfd, osec, ®num, - sizeof (regnum), &bfd_offset); - regnum = netorder32 (regnum); - - rec = record_reg_alloc (regcache, regnum); - - /* Get val. */ - bfdcore_read (core_bfd, osec, record_get_loc (rec), - rec->u.reg.len, &bfd_offset); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Reading register %d (1 " - "plus %lu plus %d bytes)\n", - rec->u.reg.num, - (unsigned long) sizeof (regnum), - rec->u.reg.len); - break; - - case record_mem: /* mem */ - /* Get len. */ - bfdcore_read (core_bfd, osec, &len, - sizeof (len), &bfd_offset); - len = netorder32 (len); - - /* Get addr. */ - bfdcore_read (core_bfd, osec, &addr, - sizeof (addr), &bfd_offset); - addr = netorder64 (addr); - - rec = record_mem_alloc (addr, len); - - /* Get val. */ - bfdcore_read (core_bfd, osec, record_get_loc (rec), - rec->u.mem.len, &bfd_offset); - - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Reading memory %s (1 plus " - "%lu plus %lu plus %d bytes)\n", - paddress (get_current_arch (), - rec->u.mem.addr), - (unsigned long) sizeof (addr), - (unsigned long) sizeof (len), - rec->u.mem.len); - break; - - case record_end: /* end */ - rec = record_end_alloc (); - record_insn_num ++; - - /* Get signal value. */ - bfdcore_read (core_bfd, osec, &signal, - sizeof (signal), &bfd_offset); - signal = netorder32 (signal); - rec->u.end.sigval = signal; - - /* Get insn count. */ - bfdcore_read (core_bfd, osec, &count, - sizeof (count), &bfd_offset); - count = netorder32 (count); - rec->u.end.insn_num = count; - record_insn_count = count + 1; - if (record_debug) - fprintf_unfiltered (gdb_stdlog, - " Reading record_end (1 + " - "%lu + %lu bytes), offset == %s\n", - (unsigned long) sizeof (signal), - (unsigned long) sizeof (count), - paddress (get_current_arch (), - bfd_offset)); - break; - - default: - error (_("Bad entry type in core file %s."), - bfd_get_filename (core_bfd)); - break; - } - - /* Add rec to record arch list. */ - record_arch_list_add (rec); - } + if (*arg != 0) + error (_("Junk after argument: %s."), arg); +} + +/* Read instruction-history modifiers from an argument string. */ + +static int +get_insn_history_modifiers (char **arg) +{ + int modifiers; + char *args; - discard_cleanups (old_cleanups); + modifiers = 0; + args = *arg; - /* 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; + if (args == NULL) + return modifiers; - /* Update record_insn_max_num. */ - if (record_insn_num > record_insn_max_num) + while (*args == '/') { - record_insn_max_num = record_insn_num; - warning (_("Auto increase record/replay buffer limit to %d."), - record_insn_max_num); - } + ++args; - /* Succeeded. */ - printf_filtered (_("Restored records from core file %s.\n"), - bfd_get_filename (core_bfd)); + if (*args == '\0') + error (_("Missing modifier.")); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); -} + for (; *args; ++args) + { + if (isspace (*args)) + break; -/* bfdcore_write -- write bytes into a core file section. */ + if (*args == '/') + continue; -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); + 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 (ret) - *offset += len; - else - error (_("Failed to write %d bytes to core file %s ('%s')."), - len, bfd_get_filename (obfd), - bfd_errmsg (bfd_get_error ())); -} + args = skip_spaces (args); + } -/* Restore the execution log from a file. We use a modified elf - corefile format, with an extra section for our data. */ + /* Update the argument string. */ + *arg = args; -static void -cmd_record_restore (char *args, int from_tty) -{ - core_file_command (args, from_tty); - record_open (args, from_tty); + return modifiers; } +/* The "record instruction-history" command. */ + static void -record_save_cleanups (void *data) +cmd_record_insn_history (char *arg, int from_tty) { - bfd *obfd = data; - char *pathname = xstrdup (bfd_get_filename (obfd)); + int flags, size; - bfd_close (obfd); - unlink (pathname); - xfree (pathname); -} + require_record_target (); -/* Save the execution log to a file. We use a modified elf corefile - format, with an extra section for our data. */ + flags = get_insn_history_modifiers (&arg); -static void -cmd_record_save (char *args, int from_tty) -{ - char *recfilename, recfilename_buffer[40]; - struct record_entry *cur_record_list; - uint32_t magic; - struct regcache *regcache; - struct gdbarch *gdbarch; - struct cleanup *old_cleanups; - struct cleanup *set_cleanups; - bfd *obfd; - int save_size = 0; - asection *osec = NULL; - int bfd_offset = 0; - - if (strcmp (current_target.to_shortname, "record") != 0) - error (_("This command can only be used with target 'record'.\n" - "Use 'target record' first.\n")); - - if (args && *args) - recfilename = args; + /* We use a signed size to also indicate the direction. Make sure that + unlimited remains unlimited. */ + size = (int) record_insn_history_size; + if (size < 0) + size = INT_MAX; + + if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0) + target_insn_history (size, flags); + else if (strcmp (arg, "-") == 0) + target_insn_history (-size, flags); else { - /* Default recfile name is "gdb_record.PID". */ - snprintf (recfilename_buffer, sizeof (recfilename_buffer), - "gdb_record.%d", PIDGET (inferior_ptid)); - recfilename = recfilename_buffer; - } + ULONGEST begin, end; - /* Open the save file. */ - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Saving execution log to core file '%s'\n", - recfilename); + begin = get_insn_number (&arg); - /* Open the output file. */ - obfd = create_gcore_bfd (recfilename); - old_cleanups = make_cleanup (record_save_cleanups, obfd); + 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_insn_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_insn_history_from (begin, -size, flags); + } + else + { + end = get_insn_number (&arg); - if (record_list->prev) - record_list = record_list->prev; - } + no_chunk (arg); - /* 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; + target_insn_history_range (begin, end, flags); + } + } else - break; + { + no_chunk (arg); + + target_insn_history_from (begin, size, flags); + } + + dont_repeat (); } +} + +/* Read function-call-history modifiers from an argument string. */ + +static int +get_call_history_modifiers (char **arg) +{ + int modifiers; + char *args; + + modifiers = 0; + args = *arg; + + if (args == NULL) + return modifiers; - /* Reverse execute to cur_record_list. */ - while (1) + while (*args == '/') { - /* Check for beginning and end of log. */ - if (record_list == cur_record_list) - break; + ++args; + + if (*args == '\0') + error (_("Missing modifier.")); + + for (; *args; ++args) + { + if (isspace (*args)) + break; + + if (*args == '/') + continue; - record_exec_insn (regcache, gdbarch, record_list); + switch (*args) + { + case 'l': + modifiers |= record_print_src_line; + break; + case 'i': + modifiers |= record_print_insn_range; + 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; } -/* record_goto_insn -- rewind the record log (forward or backward, - depending on DIR) to the given entry, changing the program state - correspondingly. */ +/* The "record function-call-history" command. */ static void -record_goto_insn (struct record_entry *entry, - enum exec_direction_kind dir) +cmd_record_call_history (char *arg, int from_tty) { - struct cleanup *set_cleanups = record_gdb_operation_disable_set (); - struct regcache *regcache = get_current_regcache (); - struct gdbarch *gdbarch = get_regcache_arch (regcache); + int flags, size; + + require_record_target (); - /* Assume everything is valid: we will hit the entry, - and we will not hit the end of the recording. */ + flags = get_call_history_modifiers (&arg); - if (dir == EXEC_FORWARD) - record_list = record_list->next; + /* We use a signed size to also indicate the direction. Make sure that + unlimited remains unlimited. */ + size = (int) record_call_history_size; + if (size < 0) + size = INT_MAX; - do + if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0) + target_call_history (size, flags); + else if (strcmp (arg, "-") == 0) + target_call_history (-size, flags); + else { - 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); -} + ULONGEST begin, end; -/* "record goto" command. Argument is an instruction number, - as given by "info record". + begin = get_insn_number (&arg); - Rewinds the recording (forward or backward) to the given instruction. */ + if (*arg == ',') + { + arg = skip_spaces (++arg); -static void -cmd_record_goto (char *arg, int from_tty) -{ - struct record_entry *p = NULL; - ULONGEST target_insn = 0; + if (*arg == '+') + { + arg += 1; + size = get_context_size (&arg); - if (arg == NULL || *arg == '\0') - error (_("Command requires an argument (insn number to go to).")); + no_chunk (arg); - 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); + target_call_history_from (begin, size, flags); + } + else if (*arg == '-') + { + arg += 1; + size = get_context_size (&arg); - for (p = &record_first; p != NULL; p = p->next) - if (p->type == record_end && p->u.end.insn_num == target_insn) - break; - } + no_chunk (arg); - 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); + target_call_history_from (begin, -size, flags); + } + else + { + end = get_insn_number (&arg); + + no_chunk (arg); + + target_call_history_range (begin, end, flags); + } + } + else + { + no_chunk (arg); + + target_call_history_from (begin, size, flags); + } + + dont_repeat (); } - registers_changed (); - reinit_frame_cache (); - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); } +/* Provide a prototype to silence -Wmissing-prototypes. */ +extern initialize_file_ftype _initialize_record; + void _initialize_record (void) { struct cmd_list_element *c; - /* Init record_first. */ - record_first.prev = NULL; - record_first.next = NULL; - record_first.type = record_end; - - init_record_ops (); - add_target (&record_ops); - init_record_core_ops (); - add_target (&record_core_ops); - - 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, _("\ +Set number of instructions to print in \"record instruction-history\"."), _("\ +Show number of instructions to print in \"record instruction-history\"."), + NULL, NULL, NULL, &set_record_cmdlist, + &show_record_cmdlist); + + add_setshow_uinteger_cmd ("function-call-history-size", no_class, + &record_call_history_size, _("\ +Set number of function to print in \"record function-call-history\"."), _("\ +Show number of functions to print in \"record function-call-history\"."), + NULL, NULL, 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); @@ -2911,12 +673,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); @@ -2928,40 +684,47 @@ 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, _("\ 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); + add_cmd ("instruction-history", class_obscure, cmd_record_insn_history, _("\ +Print disassembled instructions stored in the execution log.\n\ +With a /m modifier, source lines are included (if available).\n\ +With a /r modifier, raw instructions in hex are included.\n\ +With a /f modifier, function names are omitted.\n\ +With a /p modifier, current position markers are omitted.\n\ +With no argument, disassembles ten more instructions after the previous \ +disassembly.\n\ +\"record instruction-history -\" disassembles ten instructions before a \ +previous disassembly.\n\ +One argument specifies an instruction number as shown by 'info record', and \ +ten instructions are disassembled after that instruction.\n\ +Two arguments with comma between them specify starting and ending instruction \ +numbers to disassemble.\n\ +If the second argument is preceded by '+' or '-', it specifies the distance \ +from the first argument.\n\ +The number of instructions to disassemble can be defined with \"set record \ +instruction-history-size\"."), + &record_cmdlist); + add_cmd ("function-call-history", class_obscure, cmd_record_call_history, _("\ +Prints the execution history at function granularity.\n\ +It prints one line for each sequence of instructions that belong to the same \ +function.\n\ +Without modifiers, it prints the function name.\n\ +With a /l modifier, the source file and line number range is included.\n\ +With a /i modifier, the instruction number range is included.\n\ +With no argument, prints ten more lines after the previous ten-line print.\n\ +\"record function-call-history -\" prints ten lines before a previous ten-line \ +print.\n\ +One argument specifies a function number as shown by 'info record', and \ +ten lines are printed after that function.\n\ +Two arguments with comma between them specify a range of functions to print.\n\ +If the second argument is preceded by '+' or '-', it specifies the distance \ +from the first argument.\n\ +The number of functions to print can be defined with \"set record \ +function-call-history-size\"."), + &record_cmdlist); }