| Index: gdb/record.c
|
| diff --git a/gdb/record.c b/gdb/record.c
|
| index bb0fe5224f643d9ec65b3646984475e1065c0623..ec2a0421075bb7c5dfb339cbc531eb9c5b1db22a 100644
|
| --- a/gdb/record.c
|
| +++ b/gdb/record.c
|
| @@ -1,6 +1,6 @@
|
| /* Process record and replay target for GDB, the GNU debugger.
|
|
|
| - Copyright (C) 2008-2012 Free Software Foundation, Inc.
|
| + Copyright (C) 2008-2013 Free Software Foundation, Inc.
|
|
|
| This file is part of GDB.
|
|
|
| @@ -19,2187 +19,170 @@
|
|
|
| #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 <signal.h>
|
| -
|
| -/* 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 gdb_signal sigval;
|
| - ULONGEST insn_num;
|
| -};
|
| -
|
| -enum record_type
|
| -{
|
| - record_end = 0,
|
| - record_reg,
|
| - record_mem
|
| -};
|
| -
|
| -/* This is the data structure that makes up the execution log.
|
| -
|
| - The execution log consists of a single linked list of entries
|
| - of type "struct record_entry". It is doubly linked so that it
|
| - can be traversed in either direction.
|
| -
|
| - The start of the list is anchored by a struct called
|
| - "record_first". The pointer "record_list" either points to the
|
| - last entry that was added to the list (in record mode), or to the
|
| - next entry in the list that will be executed (in replay mode).
|
| -
|
| - Each list element (struct record_entry), in addition to next and
|
| - prev pointers, consists of a union of three entry types: mem, reg,
|
| - and end. A field called "type" determines which entry type is
|
| - represented by a given list element.
|
| -
|
| - Each instruction that is added to the execution log is represented
|
| - by a variable number of list elements ('entries'). The instruction
|
| - will have one "reg" entry for each register that is changed by
|
| - executing the instruction (including the PC in every case). It
|
| - will also have one "mem" entry for each memory change. Finally,
|
| - each instruction will have an "end" entry that separates it from
|
| - the changes associated with the next instruction. */
|
| -
|
| -struct record_entry
|
| -{
|
| - struct record_entry *prev;
|
| - struct record_entry *next;
|
| - enum record_type type;
|
| - union
|
| - {
|
| - /* reg */
|
| - struct record_reg_entry reg;
|
| - /* mem */
|
| - struct record_mem_entry mem;
|
| - /* end */
|
| - struct record_end_entry end;
|
| - } u;
|
| -};
|
| +#include <ctype.h>
|
|
|
| /* 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 gdb_signal);
|
| -static struct target_ops *record_beneath_to_wait_ops;
|
| -static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
|
| - struct target_waitstatus *,
|
| - int);
|
| -static struct target_ops *record_beneath_to_store_registers_ops;
|
| -static void (*record_beneath_to_store_registers) (struct target_ops *,
|
| - struct regcache *,
|
| - int regno);
|
| -static struct target_ops *record_beneath_to_xfer_partial_ops;
|
| -static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops *ops,
|
| - enum target_object object,
|
| - const char *annex,
|
| - gdb_byte *readbuf,
|
| - const gdb_byte *writebuf,
|
| - ULONGEST offset,
|
| - LONGEST len);
|
| -static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *,
|
| - struct bp_target_info *);
|
| -static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
|
| - struct bp_target_info *);
|
| -static int (*record_beneath_to_stopped_by_watchpoint) (void);
|
| -static int (*record_beneath_to_stopped_data_address) (struct target_ops *,
|
| - CORE_ADDR *);
|
| -static void (*record_beneath_to_async) (void (*) (enum inferior_event_type, void *), void *);
|
| -
|
| -/* Alloc and free functions for record_reg, record_mem, and record_end
|
| - entries. */
|
| -
|
| -/* Alloc a record_reg record entry. */
|
| -
|
| -static inline struct record_entry *
|
| -record_reg_alloc (struct regcache *regcache, int regnum)
|
| -{
|
| - 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 = GDB_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 gdb_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 == GDB_SIGNAL_0
|
| - || !gdbarch_process_record_signal_p (gdbarch))
|
| - ret = gdbarch_process_record (gdbarch,
|
| - regcache,
|
| - regcache_read_pc (regcache));
|
| - else
|
| - ret = gdbarch_process_record_signal (gdbarch,
|
| - regcache,
|
| - signal);
|
| -
|
| - 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 gdb_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 gdb_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 gdb_signal);
|
| -static struct target_ops *tmp_to_wait_ops;
|
| -static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
|
| - struct target_waitstatus *,
|
| - int);
|
| -static struct target_ops *tmp_to_store_registers_ops;
|
| -static void (*tmp_to_store_registers) (struct target_ops *,
|
| - struct regcache *,
|
| - int regno);
|
| -static struct target_ops *tmp_to_xfer_partial_ops;
|
| -static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops,
|
| - enum target_object object,
|
| - const char *annex,
|
| - gdb_byte *readbuf,
|
| - const gdb_byte *writebuf,
|
| - ULONGEST offset,
|
| - LONGEST len);
|
| -static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
|
| - struct bp_target_info *);
|
| -static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
|
| - struct bp_target_info *);
|
| -static int (*tmp_to_stopped_by_watchpoint) (void);
|
| -static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
|
| -static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
|
| -static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *);
|
| -
|
| -static void record_restore (void);
|
| -
|
| -/* Asynchronous signal handle registered as event loop source for when
|
| - we have pending events ready to be passed to the core. */
|
| -
|
| -static struct async_event_handler *record_async_inferior_event_token;
|
| -
|
| -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);
|
| -}
|
| -
|
| -static void record_init_record_breakpoints (void);
|
| -
|
| -/* "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);
|
| -
|
| - record_init_record_breakpoints ();
|
| -}
|
| -
|
| -/* "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. */
|
| +unsigned int record_debug = 0;
|
|
|
| -static void
|
| -record_resume (struct target_ops *ops, ptid_t ptid, int step,
|
| - enum gdb_signal signal)
|
| -{
|
| - record_resume_step = step;
|
| - record_resumed = 1;
|
| - record_execution_dir = execution_direction;
|
| -
|
| - 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 == GDB_SIGNAL_TRAP)
|
| - {
|
| - struct regcache *regcache;
|
| - struct address_space *aspace;
|
| -
|
| - /* Yes -- this is likely our single-step finishing,
|
| - but check if there's any reason the core would be
|
| - interested in the event. */
|
| -
|
| - registers_changed ();
|
| - regcache = get_current_regcache ();
|
| - tmp_pc = regcache_read_pc (regcache);
|
| - aspace = get_regcache_aspace (regcache);
|
| -
|
| - if (target_stopped_by_watchpoint ())
|
| - {
|
| - /* Always interested in watchpoints. */
|
| - }
|
| - else if (breakpoint_inserted_here_p (aspace, tmp_pc))
|
| - {
|
| - /* There is a breakpoint here. Let the core
|
| - handle it. */
|
| - if (software_breakpoint_inserted_here_p (aspace, tmp_pc))
|
| - {
|
| - struct gdbarch *gdbarch
|
| - = get_regcache_arch (regcache);
|
| - CORE_ADDR decr_pc_after_break
|
| - = gdbarch_decr_pc_after_break (gdbarch);
|
| - if (decr_pc_after_break)
|
| - regcache_write_pc (regcache,
|
| - tmp_pc + decr_pc_after_break);
|
| - }
|
| - }
|
| - else
|
| - {
|
| - /* This is a single-step trap. Record the
|
| - insn and issue another step.
|
| - FIXME: this part can be a random SIGTRAP too.
|
| - But GDB cannot handle it. */
|
| - int step = 1;
|
| -
|
| - if (!record_message_wrapper_safe (regcache,
|
| - GDB_SIGNAL_0))
|
| - {
|
| - status->kind = TARGET_WAITKIND_STOPPED;
|
| - status->value.sig = GDB_SIGNAL_0;
|
| - break;
|
| - }
|
| -
|
| - if (gdbarch_software_single_step_p (gdbarch))
|
| - {
|
| - /* Try to insert the software single step breakpoint.
|
| - If insert success, set step to 0. */
|
| - set_executing (inferior_ptid, 0);
|
| - reinit_frame_cache ();
|
| - if (gdbarch_software_single_step (gdbarch,
|
| - get_current_frame ()))
|
| - step = 0;
|
| - set_executing (inferior_ptid, 1);
|
| - }
|
| -
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "Process record: record_wait "
|
| - "issuing one more step in the target beneath\n");
|
| - record_beneath_to_resume (record_beneath_to_resume_ops,
|
| - ptid, step,
|
| - GDB_SIGNAL_0);
|
| - continue;
|
| - }
|
| - }
|
| -
|
| - /* The inferior is broken by a breakpoint or a signal. */
|
| - break;
|
| - }
|
| -
|
| - return ret;
|
| - }
|
| - }
|
| - else
|
| - {
|
| - struct regcache *regcache = get_current_regcache ();
|
| - struct gdbarch *gdbarch = get_regcache_arch (regcache);
|
| - struct address_space *aspace = get_regcache_aspace (regcache);
|
| - int continue_flag = 1;
|
| - int first_record_end = 1;
|
| - struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
|
| - CORE_ADDR tmp_pc;
|
| -
|
| - record_hw_watchpoint = 0;
|
| - status->kind = TARGET_WAITKIND_STOPPED;
|
| -
|
| - /* Check breakpoint when forward execute. */
|
| - if (execution_direction == EXEC_FORWARD)
|
| - {
|
| - tmp_pc = regcache_read_pc (regcache);
|
| - if (breakpoint_inserted_here_p (aspace, tmp_pc))
|
| - {
|
| - int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch);
|
| -
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "Process record: break at %s.\n",
|
| - paddress (gdbarch, tmp_pc));
|
| -
|
| - if (decr_pc_after_break
|
| - && !record_resume_step
|
| - && software_breakpoint_inserted_here_p (aspace, tmp_pc))
|
| - regcache_write_pc (regcache,
|
| - tmp_pc + decr_pc_after_break);
|
| - goto replay_out;
|
| - }
|
| - }
|
| -
|
| - /* If GDB is in terminal_inferior mode, it will not get the signal.
|
| - And in GDB replay mode, GDB doesn't need to be in terminal_inferior
|
| - mode, because inferior will not executed.
|
| - Then set it to terminal_ours to make GDB get the signal. */
|
| - target_terminal_ours ();
|
| -
|
| - /* In EXEC_FORWARD mode, record_list points to the tail of prev
|
| - instruction. */
|
| - if (execution_direction == EXEC_FORWARD && record_list->next)
|
| - record_list = record_list->next;
|
| -
|
| - /* Loop over the record_list, looking for the next place to
|
| - stop. */
|
| - do
|
| - {
|
| - /* Check for beginning and end of log. */
|
| - if (execution_direction == EXEC_REVERSE
|
| - && record_list == &record_first)
|
| - {
|
| - /* Hit beginning of record log in reverse. */
|
| - status->kind = TARGET_WAITKIND_NO_HISTORY;
|
| - break;
|
| - }
|
| - if (execution_direction != EXEC_REVERSE && !record_list->next)
|
| - {
|
| - /* Hit end of record log going forward. */
|
| - status->kind = TARGET_WAITKIND_NO_HISTORY;
|
| - break;
|
| - }
|
| -
|
| - record_exec_insn (regcache, gdbarch, record_list);
|
| -
|
| - if (record_list->type == record_end)
|
| - {
|
| - if (record_debug > 1)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "Process record: record_end %s to "
|
| - "inferior.\n",
|
| - host_address_to_string (record_list));
|
| -
|
| - if (first_record_end && execution_direction == EXEC_REVERSE)
|
| - {
|
| - /* When reverse excute, the first record_end is the part of
|
| - current instruction. */
|
| - first_record_end = 0;
|
| - }
|
| - else
|
| - {
|
| - /* In EXEC_REVERSE mode, this is the record_end of prev
|
| - instruction.
|
| - In EXEC_FORWARD mode, this is the record_end of current
|
| - instruction. */
|
| - /* step */
|
| - if (record_resume_step)
|
| - {
|
| - if (record_debug > 1)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "Process record: step.\n");
|
| - continue_flag = 0;
|
| - }
|
| -
|
| - /* check breakpoint */
|
| - tmp_pc = regcache_read_pc (regcache);
|
| - if (breakpoint_inserted_here_p (aspace, tmp_pc))
|
| - {
|
| - int decr_pc_after_break
|
| - = gdbarch_decr_pc_after_break (gdbarch);
|
| -
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "Process record: break "
|
| - "at %s.\n",
|
| - paddress (gdbarch, tmp_pc));
|
| - if (decr_pc_after_break
|
| - && execution_direction == EXEC_FORWARD
|
| - && !record_resume_step
|
| - && software_breakpoint_inserted_here_p (aspace,
|
| - tmp_pc))
|
| - regcache_write_pc (regcache,
|
| - tmp_pc + decr_pc_after_break);
|
| - continue_flag = 0;
|
| - }
|
| -
|
| - if (record_hw_watchpoint)
|
| - {
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "Process record: hit hw "
|
| - "watchpoint.\n");
|
| - continue_flag = 0;
|
| - }
|
| - /* Check target signal */
|
| - if (record_list->u.end.sigval != GDB_SIGNAL_0)
|
| - /* FIXME: better way to check */
|
| - continue_flag = 0;
|
| - }
|
| - }
|
| -
|
| - if (continue_flag)
|
| - {
|
| - if (execution_direction == EXEC_REVERSE)
|
| - {
|
| - if (record_list->prev)
|
| - record_list = record_list->prev;
|
| - }
|
| - else
|
| - {
|
| - if (record_list->next)
|
| - record_list = record_list->next;
|
| - }
|
| - }
|
| - }
|
| - while (continue_flag);
|
| -
|
| -replay_out:
|
| - if (record_get_sig)
|
| - status->value.sig = GDB_SIGNAL_INT;
|
| - else if (record_list->u.end.sigval != GDB_SIGNAL_0)
|
| - /* FIXME: better way to check */
|
| - status->value.sig = record_list->u.end.sigval;
|
| - else
|
| - status->value.sig = GDB_SIGNAL_TRAP;
|
| -
|
| - discard_cleanups (old_cleanups);
|
| - }
|
| -
|
| - signal (SIGINT, handle_sigint);
|
| -
|
| - do_cleanups (set_cleanups);
|
| - return inferior_ptid;
|
| -}
|
| -
|
| -static ptid_t
|
| -record_wait (struct target_ops *ops,
|
| - ptid_t ptid, struct target_waitstatus *status,
|
| - int options)
|
| -{
|
| - ptid_t return_ptid;
|
| +/* The number of instructions to print in "record instruction-history". */
|
| +static unsigned int record_insn_history_size = 10;
|
|
|
| - 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;
|
| -}
|
| +/* The variable registered as control variable in the "record
|
| + instruction-history" command. Necessary for extra input
|
| + validation. */
|
| +static unsigned int record_insn_history_size_setshow_var;
|
|
|
| -static int
|
| -record_stopped_by_watchpoint (void)
|
| -{
|
| - if (RECORD_IS_REPLAY)
|
| - return record_hw_watchpoint;
|
| - else
|
| - return record_beneath_to_stopped_by_watchpoint ();
|
| -}
|
| +/* The number of functions to print in "record function-call-history". */
|
| +static unsigned int record_call_history_size = 10;
|
|
|
| -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);
|
| -}
|
| +/* The variable registered as control variable in the "record
|
| + call-history" command. Necessary for extra input validation. */
|
| +static unsigned int record_call_history_size_setshow_var;
|
|
|
| -/* "to_disconnect" method for process record target. */
|
| +struct cmd_list_element *record_cmdlist = NULL;
|
| +struct cmd_list_element *record_goto_cmdlist = NULL;
|
| +struct cmd_list_element *set_record_cmdlist = NULL;
|
| +struct cmd_list_element *show_record_cmdlist = NULL;
|
| +struct cmd_list_element *info_record_cmdlist = NULL;
|
|
|
| -static void
|
| -record_disconnect (struct target_ops *target, char *args, int from_tty)
|
| -{
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n");
|
| +#define DEBUG(msg, args...) \
|
| + if (record_debug) \
|
| + fprintf_unfiltered (gdb_stdlog, "record: " msg "\n", ##args)
|
|
|
| - unpush_target (&record_ops);
|
| - target_disconnect (args, from_tty);
|
| -}
|
| -
|
| -/* "to_detach" method for process record target. */
|
| -
|
| -static void
|
| -record_detach (struct target_ops *ops, char *args, int from_tty)
|
| -{
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n");
|
| -
|
| - unpush_target (&record_ops);
|
| - target_detach (args, from_tty);
|
| -}
|
| -
|
| -/* "to_mourn_inferior" method for process record target. */
|
| -
|
| -static void
|
| -record_mourn_inferior (struct target_ops *ops)
|
| -{
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog, "Process record: "
|
| - "record_mourn_inferior\n");
|
| -
|
| - unpush_target (&record_ops);
|
| - target_mourn_inferior ();
|
| -}
|
| -
|
| -/* Close process record target before killing the inferior process. */
|
| -
|
| -static void
|
| -record_kill (struct target_ops *ops)
|
| -{
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
|
| -
|
| - unpush_target (&record_ops);
|
| - target_kill ();
|
| -}
|
| -
|
| -/* Record registers change (by user or by GDB) to list as an instruction. */
|
| -
|
| -static void
|
| -record_registers_change (struct regcache *regcache, int regnum)
|
| -{
|
| - /* Check record_insn_num. */
|
| - record_check_insn_num (0);
|
| -
|
| - record_arch_list_head = NULL;
|
| - record_arch_list_tail = NULL;
|
| -
|
| - if (regnum < 0)
|
| - {
|
| - int i;
|
| -
|
| - for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
|
| - {
|
| - if (record_arch_list_add_reg (regcache, i))
|
| - {
|
| - record_list_release (record_arch_list_tail);
|
| - error (_("Process record: failed to record execution log."));
|
| - }
|
| - }
|
| - }
|
| - else
|
| - {
|
| - if (record_arch_list_add_reg (regcache, regnum))
|
| - {
|
| - record_list_release (record_arch_list_tail);
|
| - error (_("Process record: failed to record execution log."));
|
| - }
|
| - }
|
| - if (record_arch_list_add_end ())
|
| - {
|
| - record_list_release (record_arch_list_tail);
|
| - error (_("Process record: failed to record execution log."));
|
| - }
|
| - record_list->next = record_arch_list_head;
|
| - record_arch_list_head->prev = record_list;
|
| - record_list = record_arch_list_tail;
|
| -
|
| - if (record_insn_num == record_insn_max_num && record_insn_max_num)
|
| - record_list_release_first ();
|
| - else
|
| - record_insn_num++;
|
| -}
|
| -
|
| -/* "to_store_registers" method for process record target. */
|
| -
|
| -static void
|
| -record_store_registers (struct target_ops *ops, struct regcache *regcache,
|
| - int regno)
|
| -{
|
| - if (!record_gdb_operation_disable)
|
| - {
|
| - if (RECORD_IS_REPLAY)
|
| - {
|
| - int n;
|
| -
|
| - /* Let user choose if he wants to write register or not. */
|
| - if (regno < 0)
|
| - n =
|
| - query (_("Because GDB is in replay mode, changing the "
|
| - "value of a register will make the execution "
|
| - "log unusable from this point onward. "
|
| - "Change all registers?"));
|
| - else
|
| - n =
|
| - query (_("Because GDB is in replay mode, changing the value "
|
| - "of a register will make the execution log unusable "
|
| - "from this point onward. Change register %s?"),
|
| - gdbarch_register_name (get_regcache_arch (regcache),
|
| - regno));
|
| -
|
| - if (!n)
|
| - {
|
| - /* Invalidate the value of regcache that was set in function
|
| - "regcache_raw_write". */
|
| - if (regno < 0)
|
| - {
|
| - int i;
|
| -
|
| - for (i = 0;
|
| - i < gdbarch_num_regs (get_regcache_arch (regcache));
|
| - i++)
|
| - regcache_invalidate (regcache, i);
|
| - }
|
| - else
|
| - regcache_invalidate (regcache, regno);
|
| -
|
| - error (_("Process record canceled the operation."));
|
| - }
|
| -
|
| - /* Destroy the record from here forward. */
|
| - record_list_release_following (record_list);
|
| - }
|
| -
|
| - record_registers_change (regcache, regno);
|
| - }
|
| - record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
|
| - regcache, regno);
|
| -}
|
| -
|
| -/* "to_xfer_partial" method. Behavior is conditional on RECORD_IS_REPLAY.
|
| - In replay mode, we cannot write memory unles we are willing to
|
| - invalidate the record/replay log from this point forward. */
|
| -
|
| -static LONGEST
|
| -record_xfer_partial (struct target_ops *ops, enum target_object object,
|
| - const char *annex, gdb_byte *readbuf,
|
| - const gdb_byte *writebuf, ULONGEST offset, LONGEST len)
|
| -{
|
| - if (!record_gdb_operation_disable
|
| - && (object == TARGET_OBJECT_MEMORY
|
| - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
|
| - {
|
| - if (RECORD_IS_REPLAY)
|
| - {
|
| - /* Let user choose if he wants to write memory or not. */
|
| - if (!query (_("Because GDB is in replay mode, writing to memory "
|
| - "will make the execution log unusable from this "
|
| - "point onward. Write memory at address %s?"),
|
| - paddress (target_gdbarch, offset)))
|
| - error (_("Process record canceled the operation."));
|
| -
|
| - /* Destroy the record from here forward. */
|
| - record_list_release_following (record_list);
|
| - }
|
| -
|
| - /* Check record_insn_num */
|
| - record_check_insn_num (0);
|
| -
|
| - /* Record registers change to list as an instruction. */
|
| - record_arch_list_head = NULL;
|
| - record_arch_list_tail = NULL;
|
| - if (record_arch_list_add_mem (offset, len))
|
| - {
|
| - record_list_release (record_arch_list_tail);
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "Process record: failed to record "
|
| - "execution log.");
|
| - return -1;
|
| - }
|
| - if (record_arch_list_add_end ())
|
| - {
|
| - record_list_release (record_arch_list_tail);
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "Process record: failed to record "
|
| - "execution log.");
|
| - return -1;
|
| - }
|
| - record_list->next = record_arch_list_head;
|
| - record_arch_list_head->prev = record_list;
|
| - record_list = record_arch_list_tail;
|
| -
|
| - if (record_insn_num == record_insn_max_num && record_insn_max_num)
|
| - record_list_release_first ();
|
| - else
|
| - record_insn_num++;
|
| - }
|
| -
|
| - return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
|
| - object, annex, readbuf, writebuf,
|
| - offset, len);
|
| -}
|
| -
|
| -/* This structure represents a breakpoint inserted while the record
|
| - target is active. We use this to know when to install/remove
|
| - breakpoints in/from the target beneath. For example, a breakpoint
|
| - may be inserted while recording, but removed when not replaying nor
|
| - recording. In that case, the breakpoint had not been inserted on
|
| - the target beneath, so we should not try to remove it there. */
|
| -
|
| -struct record_breakpoint
|
| -{
|
| - /* The address and address space the breakpoint was set at. */
|
| - struct address_space *address_space;
|
| - CORE_ADDR addr;
|
| -
|
| - /* True when the breakpoint has been also installed in the target
|
| - beneath. This will be false for breakpoints set during replay or
|
| - when recording. */
|
| - int in_target_beneath;
|
| -};
|
| -
|
| -typedef struct record_breakpoint *record_breakpoint_p;
|
| -DEF_VEC_P(record_breakpoint_p);
|
| -
|
| -/* The list of breakpoints inserted while the record target is
|
| - active. */
|
| -VEC(record_breakpoint_p) *record_breakpoints = NULL;
|
| -
|
| -static void
|
| -record_sync_record_breakpoints (struct bp_location *loc, void *data)
|
| -{
|
| - if (loc->loc_type != bp_loc_software_breakpoint)
|
| - return;
|
| -
|
| - if (loc->inserted)
|
| - {
|
| - struct record_breakpoint *bp = XNEW (struct record_breakpoint);
|
| -
|
| - bp->addr = loc->target_info.placed_address;
|
| - bp->address_space = loc->target_info.placed_address_space;
|
| -
|
| - bp->in_target_beneath = 1;
|
| -
|
| - VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
|
| - }
|
| -}
|
| -
|
| -/* Sync existing breakpoints to record_breakpoints. */
|
| -
|
| -static void
|
| -record_init_record_breakpoints (void)
|
| -{
|
| - VEC_free (record_breakpoint_p, record_breakpoints);
|
| -
|
| - iterate_over_bp_locations (record_sync_record_breakpoints);
|
| -}
|
| -
|
| -/* Behavior is conditional on RECORD_IS_REPLAY. We will not actually
|
| - insert or remove breakpoints in the real target when replaying, nor
|
| - when recording. */
|
| -
|
| -static int
|
| -record_insert_breakpoint (struct gdbarch *gdbarch,
|
| - struct bp_target_info *bp_tgt)
|
| -{
|
| - struct record_breakpoint *bp;
|
| - int in_target_beneath = 0;
|
| -
|
| - if (!RECORD_IS_REPLAY)
|
| - {
|
| - /* When recording, we currently always single-step, so we don't
|
| - really need to install regular breakpoints in the inferior.
|
| - However, we do have to insert software single-step
|
| - breakpoints, in case the target can't hardware step. To keep
|
| - things single, we always insert. */
|
| - struct cleanup *old_cleanups;
|
| - int ret;
|
| -
|
| - old_cleanups = record_gdb_operation_disable_set ();
|
| - ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt);
|
| - do_cleanups (old_cleanups);
|
| -
|
| - if (ret != 0)
|
| - return ret;
|
| -
|
| - in_target_beneath = 1;
|
| - }
|
| -
|
| - bp = XNEW (struct record_breakpoint);
|
| - bp->addr = bp_tgt->placed_address;
|
| - bp->address_space = bp_tgt->placed_address_space;
|
| - bp->in_target_beneath = in_target_beneath;
|
| - VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
|
| - return 0;
|
| -}
|
| -
|
| -/* "to_remove_breakpoint" method for process record target. */
|
| -
|
| -static int
|
| -record_remove_breakpoint (struct gdbarch *gdbarch,
|
| - struct bp_target_info *bp_tgt)
|
| -{
|
| - struct record_breakpoint *bp;
|
| - int ix;
|
| -
|
| - for (ix = 0;
|
| - VEC_iterate (record_breakpoint_p, record_breakpoints, ix, bp);
|
| - ++ix)
|
| - {
|
| - if (bp->addr == bp_tgt->placed_address
|
| - && bp->address_space == bp_tgt->placed_address_space)
|
| - {
|
| - if (bp->in_target_beneath)
|
| - {
|
| - struct cleanup *old_cleanups;
|
| - int ret;
|
| -
|
| - old_cleanups = record_gdb_operation_disable_set ();
|
| - ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt);
|
| - do_cleanups (old_cleanups);
|
| -
|
| - if (ret != 0)
|
| - return ret;
|
| - }
|
| -
|
| - VEC_unordered_remove (record_breakpoint_p, record_breakpoints, ix);
|
| - return 0;
|
| - }
|
| - }
|
| -
|
| - gdb_assert_not_reached ("removing unknown breakpoint");
|
| -}
|
| -
|
| -/* "to_can_execute_reverse" method for process record target. */
|
| -
|
| -static int
|
| -record_can_execute_reverse (void)
|
| -{
|
| - return 1;
|
| -}
|
| -
|
| -/* "to_get_bookmark" method for process record and prec over core. */
|
| -
|
| -static gdb_byte *
|
| -record_get_bookmark (char *args, int from_tty)
|
| -{
|
| - gdb_byte *ret = NULL;
|
| -
|
| - /* Return stringified form of instruction count. */
|
| - if (record_list && record_list->type == record_end)
|
| - ret = xstrdup (pulongest (record_list->u.end.insn_num));
|
| -
|
| - if (record_debug)
|
| - {
|
| - if (ret)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "record_get_bookmark returns %s\n", ret);
|
| - else
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "record_get_bookmark returns NULL\n");
|
| - }
|
| - return ret;
|
| -}
|
| -
|
| -/* The implementation of the command "record goto". */
|
| -static void cmd_record_goto (char *, int);
|
| -
|
| -/* "to_goto_bookmark" method for process record and prec over core. */
|
| -
|
| -static void
|
| -record_goto_bookmark (gdb_byte *bookmark, int from_tty)
|
| -{
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog,
|
| - "record_goto_bookmark receives %s\n", bookmark);
|
| -
|
| - if (bookmark[0] == '\'' || bookmark[0] == '\"')
|
| - {
|
| - if (bookmark[strlen (bookmark) - 1] != bookmark[0])
|
| - error (_("Unbalanced quotes: %s"), bookmark);
|
| -
|
| - /* Strip trailing quote. */
|
| - bookmark[strlen (bookmark) - 1] = '\0';
|
| - /* Strip leading quote. */
|
| - bookmark++;
|
| - /* Pass along to cmd_record_goto. */
|
| - }
|
| -
|
| - cmd_record_goto ((char *) bookmark, from_tty);
|
| - return;
|
| -}
|
| -
|
| -static void
|
| -record_async (void (*callback) (enum inferior_event_type event_type,
|
| - void *context), void *context)
|
| -{
|
| - /* If we're on top of a line target (e.g., linux-nat, remote), then
|
| - set it to async mode as well. Will be NULL if we're sitting on
|
| - top of the core target, for "record restore". */
|
| - if (record_beneath_to_async != NULL)
|
| - record_beneath_to_async (callback, context);
|
| -}
|
| -
|
| -static int
|
| -record_can_async_p (void)
|
| -{
|
| - /* We only enable async when the user specifically asks for it. */
|
| - return target_async_permitted;
|
| -}
|
| +/* Find the record target in the target stack. */
|
|
|
| -static int
|
| -record_is_async_p (void)
|
| +static struct target_ops *
|
| +find_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;
|
| -}
|
| + for (t = current_target.beneath; t != NULL; t = t->beneath)
|
| + if (t->to_stratum == record_stratum)
|
| + return t;
|
|
|
| -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 NULL;
|
| }
|
|
|
| -/* "to_resume" method for prec over corefile. */
|
| +/* Check that recording is active. Throw an error, if it isn't. */
|
|
|
| -static void
|
| -record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
|
| - enum gdb_signal signal)
|
| +static struct target_ops *
|
| +require_record_target (void)
|
| {
|
| - record_resume_step = step;
|
| - record_resumed = 1;
|
| - record_execution_dir = execution_direction;
|
| + struct target_ops *t;
|
|
|
| - /* 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);
|
| + t = find_record_target ();
|
| + if (t == NULL)
|
| + error (_("No record target is currently active.\n"
|
| + "Use one of the \"target record-<tab><tab>\" commands first."));
|
|
|
| - /* Notify the event loop there's an event to wait for. */
|
| - mark_async_event_handler (record_async_inferior_event_token);
|
| - }
|
| + return t;
|
| }
|
|
|
| -/* "to_kill" method for prec over corefile. */
|
| +/* See record.h. */
|
|
|
| -static void
|
| -record_core_kill (struct target_ops *ops)
|
| +int
|
| +record_read_memory (struct gdbarch *gdbarch,
|
| + CORE_ADDR memaddr, gdb_byte *myaddr,
|
| + ssize_t len)
|
| {
|
| - if (record_debug)
|
| - fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n");
|
| + int ret = target_read_memory (memaddr, myaddr, len);
|
|
|
| - unpush_target (&record_core_ops);
|
| + if (ret != 0)
|
| + DEBUG ("error reading memory at addr %s len = %ld.\n",
|
| + paddress (gdbarch, memaddr), (long) len);
|
| +
|
| + return ret;
|
| }
|
|
|
| -/* "to_fetch_registers" method for prec over corefile. */
|
| +/* Stop recording. */
|
|
|
| static void
|
| -record_core_fetch_registers (struct target_ops *ops,
|
| - struct regcache *regcache,
|
| - int regno)
|
| +record_stop (struct target_ops *t)
|
| {
|
| - if (regno < 0)
|
| - {
|
| - int num = gdbarch_num_regs (get_regcache_arch (regcache));
|
| - int i;
|
| + DEBUG ("stop %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);
|
| + if (t->to_stop_recording != NULL)
|
| + t->to_stop_recording ();
|
| }
|
|
|
| -/* "to_prepare_to_store" method for prec over corefile. */
|
| +/* Unpush the record target. */
|
|
|
| static void
|
| -record_core_prepare_to_store (struct regcache *regcache)
|
| +record_unpush (struct target_ops *t)
|
| {
|
| + DEBUG ("unpush %s", t->to_shortname);
|
| +
|
| + unpush_target (t);
|
| }
|
|
|
| -/* "to_store_registers" method for prec over corefile. */
|
| +/* See record.h. */
|
|
|
| -static void
|
| -record_core_store_registers (struct target_ops *ops,
|
| - struct regcache *regcache,
|
| - int regno)
|
| +void
|
| +record_disconnect (struct target_ops *t, char *args, int from_tty)
|
| {
|
| - 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."));
|
| + gdb_assert (t->to_stratum == record_stratum);
|
| +
|
| + DEBUG ("disconnect %s", t->to_shortname);
|
| +
|
| + 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, const 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. */
|
| @@ -2217,7 +200,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
|
| @@ -2226,21 +209,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. */
|
| @@ -2248,34 +235,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)
|
| @@ -2285,676 +258,445 @@ 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;
|
| + }
|
| +
|
| + printf_filtered (_("Active record target: %s\n"), t->to_shortname);
|
| + if (t->to_info_record != NULL)
|
| + t->to_info_record ();
|
| +}
|
|
|
| - /* Find entry for first actual instruction in the log. */
|
| - for (p = record_first.next;
|
| - p != NULL && p->type != record_end;
|
| - p = p->next)
|
| - ;
|
| +/* The "record save" command. */
|
|
|
| - /* 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"));
|
| - }
|
| - }
|
| +static void
|
| +cmd_record_save (char *args, int from_tty)
|
| +{
|
| + char *recfilename, recfilename_buffer[40];
|
| +
|
| + require_record_target ();
|
| +
|
| + if (args != NULL && *args != 0)
|
| + recfilename = args;
|
| else
|
| {
|
| - printf_filtered (_("target record is not active.\n"));
|
| + /* Default recfile name is "gdb_record.PID". */
|
| + xsnprintf (recfilename_buffer, sizeof (recfilename_buffer),
|
| + "gdb_record.%d", ptid_get_pid (inferior_ptid));
|
| + recfilename = recfilename_buffer;
|
| }
|
|
|
| - /* Display max log size. */
|
| - printf_filtered (_("Max logged instructions is %d.\n"),
|
| - record_insn_max_num);
|
| + target_save_record (recfilename);
|
| }
|
|
|
| -/* Record log save-file format
|
| - Version 1 (never released)
|
| -
|
| - Header:
|
| - 4 bytes: magic number htonl(0x20090829).
|
| - NOTE: be sure to change whenever this file format changes!
|
| -
|
| - Records:
|
| - record_end:
|
| - 1 byte: record type (record_end, see enum record_type).
|
| - record_reg:
|
| - 1 byte: record type (record_reg, see enum record_type).
|
| - 8 bytes: register id (network byte order).
|
| - MAX_REGISTER_SIZE bytes: register value.
|
| - record_mem:
|
| - 1 byte: record type (record_mem, see enum record_type).
|
| - 8 bytes: memory length (network byte order).
|
| - 8 bytes: memory address (network byte order).
|
| - n bytes: memory value (n == memory length).
|
| -
|
| - Version 2
|
| - 4 bytes: magic number netorder32(0x20091016).
|
| - NOTE: be sure to change whenever this file format changes!
|
| -
|
| - Records:
|
| - record_end:
|
| - 1 byte: record type (record_end, see enum record_type).
|
| - 4 bytes: signal
|
| - 4 bytes: instruction count
|
| - record_reg:
|
| - 1 byte: record type (record_reg, see enum record_type).
|
| - 4 bytes: register id (network byte order).
|
| - n bytes: register value (n == actual register size).
|
| - (eg. 4 bytes for x86 general registers).
|
| - record_mem:
|
| - 1 byte: record type (record_mem, see enum record_type).
|
| - 4 bytes: memory length (network byte order).
|
| - 8 bytes: memory address (network byte order).
|
| - n bytes: memory value (n == memory length).
|
| -
|
| -*/
|
| -
|
| -/* bfdcore_read -- read bytes from a core file section. */
|
| -
|
| -static inline void
|
| -bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
|
| +/* "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);
|
| + ULONGEST insn;
|
|
|
| - if (ret)
|
| - *offset += len;
|
| - else
|
| - error (_("Failed to read %d bytes from core file %s ('%s')."),
|
| - len, bfd_get_filename (obfd),
|
| - bfd_errmsg (bfd_get_error ()));
|
| + if (arg == NULL || *arg == '\0')
|
| + error (_("Command requires an argument (insn number to go to)."));
|
| +
|
| + insn = parse_and_eval_long (arg);
|
| +
|
| + require_record_target ();
|
| + target_goto_record (insn);
|
| }
|
|
|
| -static inline uint64_t
|
| -netorder64 (uint64_t input)
|
| +/* The "record goto begin" command. */
|
| +
|
| +static void
|
| +cmd_record_goto_begin (char *arg, int from_tty)
|
| {
|
| - uint64_t ret;
|
| + if (arg != NULL && *arg != '\0')
|
| + error (_("Junk after argument: %s."), arg);
|
|
|
| - store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret),
|
| - BFD_ENDIAN_BIG, input);
|
| - return ret;
|
| + require_record_target ();
|
| + target_goto_record_begin ();
|
| +}
|
| +
|
| +/* The "record goto end" command. */
|
| +
|
| +static void
|
| +cmd_record_goto_end (char *arg, int from_tty)
|
| +{
|
| + if (arg != NULL && *arg != '\0')
|
| + error (_("Junk after argument: %s."), arg);
|
| +
|
| + require_record_target ();
|
| + target_goto_record_end ();
|
| }
|
|
|
| -static inline uint32_t
|
| -netorder32 (uint32_t input)
|
| +/* Read an instruction number from an argument string. */
|
| +
|
| +static ULONGEST
|
| +get_insn_number (char **arg)
|
| {
|
| - uint32_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);
|
| +
|
| + if (!isdigit (*pos))
|
| + error (_("Expected positive number, got: %s."), pos);
|
| +
|
| + 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;
|
| +
|
| + if (*args == '\0')
|
| + error (_("Missing modifier."));
|
| +
|
| + for (; *args; ++args)
|
| + {
|
| + if (isspace (*args))
|
| + break;
|
| +
|
| + if (*args == '/')
|
| + continue;
|
| +
|
| + 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);
|
| + }
|
| + }
|
| +
|
| + args = skip_spaces (args);
|
| }
|
|
|
| - /* Succeeded. */
|
| - printf_filtered (_("Restored records from core file %s.\n"),
|
| - bfd_get_filename (core_bfd));
|
| + /* Update the argument string. */
|
| + *arg = args;
|
|
|
| - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
|
| + return modifiers;
|
| }
|
|
|
| -/* bfdcore_write -- write bytes into a core file section. */
|
| +/* The "set record instruction-history-size / set record
|
| + function-call-history-size" commands are unsigned, with UINT_MAX
|
| + meaning unlimited. The target interfaces works with signed int
|
| + though, to indicate direction, so map "unlimited" to INT_MAX, which
|
| + is about the same as unlimited in practice. If the user does have
|
| + a log that huge, she can fetch it in chunks across several requests,
|
| + but she'll likely have other problems first... */
|
|
|
| -static inline void
|
| -bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
|
| +static int
|
| +command_size_to_target_size (unsigned int size)
|
| {
|
| - int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
|
| + gdb_assert (size <= INT_MAX || size == UINT_MAX);
|
|
|
| - if (ret)
|
| - *offset += len;
|
| + if (size == UINT_MAX)
|
| + return INT_MAX;
|
| else
|
| - error (_("Failed to write %d bytes to core file %s ('%s')."),
|
| - len, bfd_get_filename (obfd),
|
| - bfd_errmsg (bfd_get_error ()));
|
| + return size;
|
| }
|
|
|
| -/* Restore the execution log from a file. We use a modified elf
|
| - corefile format, with an extra section for our data. */
|
| +/* The "record instruction-history" command. */
|
|
|
| static void
|
| -cmd_record_restore (char *args, int from_tty)
|
| +cmd_record_insn_history (char *arg, int from_tty)
|
| {
|
| - core_file_command (args, from_tty);
|
| - record_open (args, from_tty);
|
| -}
|
| + int flags, size;
|
|
|
| -static void
|
| -record_save_cleanups (void *data)
|
| -{
|
| - bfd *obfd = data;
|
| - char *pathname = xstrdup (bfd_get_filename (obfd));
|
| + require_record_target ();
|
|
|
| - bfd_close (obfd);
|
| - unlink (pathname);
|
| - xfree (pathname);
|
| -}
|
| + flags = get_insn_history_modifiers (&arg);
|
|
|
| -/* Save the execution log to a file. We use a modified elf corefile
|
| - format, with an extra section for our data. */
|
| + size = command_size_to_target_size (record_insn_history_size);
|
|
|
| -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;
|
| + 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;
|
|
|
| - /* Reverse execute to cur_record_list. */
|
| - while (1)
|
| + if (args == NULL)
|
| + return modifiers;
|
| +
|
| + 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;
|
|
|
| - record_exec_insn (regcache, gdbarch, record_list);
|
| + if (*args == '/')
|
| + continue;
|
| +
|
| + 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;
|
|
|
| - /* Assume everything is valid: we will hit the entry,
|
| - and we will not hit the end of the recording. */
|
| + require_record_target ();
|
|
|
| - if (dir == EXEC_FORWARD)
|
| - record_list = record_list->next;
|
| + flags = get_call_history_modifiers (&arg);
|
|
|
| - do
|
| + size = command_size_to_target_size (record_call_history_size);
|
| +
|
| + if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0)
|
| + target_call_history (size, flags);
|
| + else if (strcmp (arg, "-") == 0)
|
| + target_call_history (-size, flags);
|
| + else
|
| {
|
| - 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);
|
| + 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 ();
|
| }
|
| - else
|
| +}
|
| +
|
| +/* Helper for "set record instruction-history-size" and "set record
|
| + function-call-history-size" input validation. COMMAND_VAR is the
|
| + variable registered in the command as control variable. *SETTING
|
| + is the real setting the command allows changing. */
|
| +
|
| +static void
|
| +validate_history_size (unsigned int *command_var, unsigned int *setting)
|
| +{
|
| + if (*command_var != UINT_MAX && *command_var > INT_MAX)
|
| {
|
| - printf_filtered (_("Go backward to insn number %s\n"),
|
| - pulongest (target_insn));
|
| - record_goto_insn (p, EXEC_REVERSE);
|
| + unsigned int new_value = *command_var;
|
| +
|
| + /* Restore previous value. */
|
| + *command_var = *setting;
|
| + error (_("integer %u out of range"), new_value);
|
| }
|
| - registers_changed ();
|
| - reinit_frame_cache ();
|
| - print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
|
| +
|
| + /* Commit new value. */
|
| + *setting = *command_var;
|
| +}
|
| +
|
| +/* Called by do_setshow_command. We only want values in the
|
| + [0..INT_MAX] range, while the command's machinery accepts
|
| + [0..UINT_MAX]. See command_size_to_target_size. */
|
| +
|
| +static void
|
| +set_record_insn_history_size (char *args, int from_tty,
|
| + struct cmd_list_element *c)
|
| +{
|
| + validate_history_size (&record_insn_history_size_setshow_var,
|
| + &record_insn_history_size);
|
| +}
|
| +
|
| +/* Called by do_setshow_command. We only want values in the
|
| + [0..INT_MAX] range, while the command's machinery accepts
|
| + [0..UINT_MAX]. See command_size_to_target_size. */
|
| +
|
| +static void
|
| +set_record_call_history_size (char *args, int from_tty,
|
| + struct cmd_list_element *c)
|
| +{
|
| + validate_history_size (&record_call_history_size_setshow_var,
|
| + &record_call_history_size);
|
| }
|
|
|
| /* Provide a prototype to silence -Wmissing-prototypes. */
|
| @@ -2965,26 +707,32 @@ _initialize_record (void)
|
| {
|
| struct cmd_list_element *c;
|
|
|
| - /* Init record_first. */
|
| - record_first.prev = NULL;
|
| - record_first.next = NULL;
|
| - record_first.type = record_end;
|
| -
|
| - init_record_ops ();
|
| - add_target (&record_ops);
|
| - init_record_core_ops ();
|
| - add_target (&record_core_ops);
|
| -
|
| - add_setshow_zinteger_cmd ("record", no_class, &record_debug,
|
| - _("Set debugging of record/replay feature."),
|
| - _("Show debugging of record/replay feature."),
|
| - _("When enabled, debugging output for "
|
| - "record/replay feature is displayed."),
|
| - NULL, show_record_debug, &setdebuglist,
|
| - &showdebuglist);
|
| + add_setshow_zuinteger_cmd ("record", no_class, &record_debug,
|
| + _("Set debugging of record/replay feature."),
|
| + _("Show debugging of record/replay feature."),
|
| + _("When enabled, debugging output for "
|
| + "record/replay feature is displayed."),
|
| + NULL, show_record_debug, &setdebuglist,
|
| + &showdebuglist);
|
| +
|
| + add_setshow_uinteger_cmd ("instruction-history-size", no_class,
|
| + &record_insn_history_size_setshow_var, _("\
|
| +Set number of instructions to print in \"record instruction-history\"."), _("\
|
| +Show number of instructions to print in \"record instruction-history\"."), _("\
|
| +A size of \"unlimited\" means unlimited instructions. The default is 10."),
|
| + set_record_insn_history_size, NULL,
|
| + &set_record_cmdlist, &show_record_cmdlist);
|
| +
|
| + add_setshow_uinteger_cmd ("function-call-history-size", no_class,
|
| + &record_call_history_size_setshow_var, _("\
|
| +Set number of function to print in \"record function-call-history\"."), _("\
|
| +Show number of functions to print in \"record function-call-history\"."), _("\
|
| +A size of \"unlimited\" means unlimited lines. The default is 10."),
|
| + set_record_call_history_size, NULL,
|
| + &set_record_cmdlist, &show_record_cmdlist);
|
|
|
| c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
|
| - _("Abbreviated form of \"target record\" command."),
|
| + _("Start recording."),
|
| &record_cmdlist, "record ", 0, &cmdlist);
|
| set_cmd_completer (c, filename_completer);
|
|
|
| @@ -3009,12 +757,6 @@ Default filename is 'gdb_record.<process_id>'."),
|
| &record_cmdlist);
|
| set_cmd_completer (c, filename_completer);
|
|
|
| - c = add_cmd ("restore", class_obscure, cmd_record_restore,
|
| - _("Restore the execution log from a file.\n\
|
| -Argument is filename. File must be created with 'record save'."),
|
| - &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);
|
| @@ -3026,40 +768,60 @@ Argument is filename. File must be created with 'record save'."),
|
| &record_cmdlist);
|
| add_alias_cmd ("s", "stop", class_obscure, 1, &record_cmdlist);
|
|
|
| - /* Record instructions number limit command. */
|
| - add_setshow_boolean_cmd ("stop-at-limit", no_class,
|
| - &record_stop_at_limit, _("\
|
| -Set whether record/replay stops when record/replay buffer becomes full."), _("\
|
| -Show whether record/replay stops when record/replay buffer becomes full."),
|
| - _("Default is ON.\n\
|
| -When ON, if the record/replay buffer becomes full, ask user what to do.\n\
|
| -When OFF, if the record/replay buffer becomes full,\n\
|
| -delete the oldest recorded instruction to make room for each new one."),
|
| - NULL, NULL,
|
| - &set_record_cmdlist, &show_record_cmdlist);
|
| - add_setshow_uinteger_cmd ("insn-number-max", no_class,
|
| - &record_insn_max_num,
|
| - _("Set record/replay buffer limit."),
|
| - _("Show record/replay buffer limit."), _("\
|
| -Set the maximum number of instructions to be stored in the\n\
|
| -record/replay buffer. Zero means unlimited. Default is 200000."),
|
| - set_record_insn_max_num,
|
| - NULL, &set_record_cmdlist, &show_record_cmdlist);
|
| -
|
| - add_cmd ("goto", class_obscure, cmd_record_goto, _("\
|
| + add_prefix_cmd ("goto", class_obscure, cmd_record_goto, _("\
|
| Restore the program to its state at instruction number N.\n\
|
| Argument is instruction number, as shown by 'info record'."),
|
| - &record_cmdlist);
|
| -
|
| - add_setshow_boolean_cmd ("memory-query", no_class,
|
| - &record_memory_query, _("\
|
| -Set whether query if PREC cannot record memory change of next instruction."),
|
| - _("\
|
| -Show whether query if PREC cannot record memory change of next instruction."),
|
| - _("\
|
| -Default is OFF.\n\
|
| -When ON, query if PREC cannot record memory change of next instruction."),
|
| - NULL, NULL,
|
| - &set_record_cmdlist, &show_record_cmdlist);
|
| + &record_goto_cmdlist, "record goto ", 1, &record_cmdlist);
|
| +
|
| + add_cmd ("begin", class_obscure, cmd_record_goto_begin,
|
| + _("Go to the beginning of the execution log."),
|
| + &record_goto_cmdlist);
|
| + add_alias_cmd ("start", "begin", class_obscure, 1, &record_goto_cmdlist);
|
| +
|
| + add_cmd ("end", class_obscure, cmd_record_goto_end,
|
| + _("Go to the end of the execution log."),
|
| + &record_goto_cmdlist);
|
| +
|
| + add_cmd ("instruction-history", class_obscure, cmd_record_insn_history, _("\
|
| +Print disassembled instructions stored in the execution log.\n\
|
| +With a /m modifier, source lines are included (if available).\n\
|
| +With a /r modifier, raw instructions in hex are included.\n\
|
| +With a /f modifier, function names are omitted.\n\
|
| +With a /p modifier, current position markers are omitted.\n\
|
| +With no argument, disassembles ten more instructions after the previous \
|
| +disassembly.\n\
|
| +\"record instruction-history -\" disassembles ten instructions before a \
|
| +previous disassembly.\n\
|
| +One argument specifies an instruction number as shown by 'info record', and \
|
| +ten instructions are disassembled after that instruction.\n\
|
| +Two arguments with comma between them specify starting and ending instruction \
|
| +numbers to disassemble.\n\
|
| +If the second argument is preceded by '+' or '-', it specifies the distance \
|
| +from the first argument.\n\
|
| +The number of instructions to disassemble can be defined with \"set record \
|
| +instruction-history-size\"."),
|
| + &record_cmdlist);
|
| +
|
| + add_cmd ("function-call-history", class_obscure, cmd_record_call_history, _("\
|
| +Prints the execution history at function granularity.\n\
|
| +It prints one line for each sequence of instructions that belong to the same \
|
| +function.\n\
|
| +Without modifiers, it prints the function name.\n\
|
| +With a /l modifier, the source file and line number range is included.\n\
|
| +With a /i modifier, the instruction number range is included.\n\
|
| +With no argument, prints ten more lines after the previous ten-line print.\n\
|
| +\"record function-call-history -\" prints ten lines before a previous ten-line \
|
| +print.\n\
|
| +One argument specifies a function number as shown by 'info record', and \
|
| +ten lines are printed after that function.\n\
|
| +Two arguments with comma between them specify a range of functions to print.\n\
|
| +If the second argument is preceded by '+' or '-', it specifies the distance \
|
| +from the first argument.\n\
|
| +The number of functions to print can be defined with \"set record \
|
| +function-call-history-size\"."),
|
| + &record_cmdlist);
|
|
|
| + /* Sync command control variables. */
|
| + record_insn_history_size_setshow_var = record_insn_history_size;
|
| + record_call_history_size_setshow_var = record_call_history_size;
|
| }
|
|
|