X-Git-Url: http://git.efficios.com/?a=blobdiff_plain;f=gdb%2Frecord.c;h=60de0853a8e5bc5bddf8136f73770fa8fc8ea801;hb=496c2508ac12824a98d5ded95fa999e9ebc6c34b;hp=8b56010e29b15a21abaa73e0d72ed2b8246a0db0;hpb=5d40bb85448aeaf7aaa5f9c13e0aafc6d506c296;p=deliverable%2Fbinutils-gdb.git diff --git a/gdb/record.c b/gdb/record.c index 8b56010e29..60de0853a8 100644 --- a/gdb/record.c +++ b/gdb/record.c @@ -1,6 +1,6 @@ /* Process record and replay target for GDB, the GNU debugger. - Copyright (C) 2008, 2009 Free Software Foundation, Inc. + Copyright (C) 2008, 2009, 2010, 2011 Free Software Foundation, Inc. This file is part of GDB. @@ -23,15 +23,45 @@ #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 +/* 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 @@ -43,12 +73,6 @@ Each struct record_entry is linked to "record_list" by "prev" and "next" pointers. */ -struct record_reg_entry -{ - int num; - gdb_byte *val; -}; - struct record_mem_entry { CORE_ADDR addr; @@ -56,12 +80,28 @@ struct record_mem_entry /* Set this flag if target memory for this entry can no longer be accessed. */ int mem_entry_not_accessible; - gdb_byte *val; + union + { + gdb_byte *ptr; + gdb_byte buf[sizeof (gdb_byte *)]; + } u; +}; + +struct record_reg_entry +{ + unsigned short num; + unsigned short len; + union + { + gdb_byte *ptr; + gdb_byte buf[2 * sizeof (gdb_byte *)]; + } u; }; struct record_end_entry { enum target_signal sigval; + ULONGEST insn_num; }; enum record_type @@ -71,6 +111,30 @@ enum record_type 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; @@ -90,7 +154,39 @@ struct record_entry /* This is the debug switch for process record. */ int record_debug = 0; -/* These list is for execution log. */ +/* 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; @@ -98,11 +194,17 @@ static struct record_entry *record_arch_list_tail = NULL; /* 1 ask user. 0 auto delete the last struct record_entry. */ static int record_stop_at_limit = 1; -static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; +/* 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; @@ -128,89 +230,191 @@ 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) { - struct record_entry *tmp; - if (!rec) return; while (rec->next) - { - rec = rec->next; - } + rec = rec->next; while (rec->prev) { - tmp = rec; rec = rec->prev; - if (tmp->type == record_reg) - xfree (tmp->u.reg.val); - else if (tmp->type == record_mem) - xfree (tmp->u.mem.val); - xfree (tmp); + record_entry_release (rec->next); } - if (rec != &record_first) - xfree (rec); + 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_next (void) +record_list_release_following (struct record_entry *rec) { - struct record_entry *rec = record_list; struct record_entry *tmp = rec->next; + rec->next = NULL; while (tmp) { rec = tmp->next; - if (tmp->type == record_end) - record_insn_num--; - else if (tmp->type == record_reg) - xfree (tmp->u.reg.val); - else if (tmp->type == record_mem) - xfree (tmp->u.mem.val); - xfree (tmp); + 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 = NULL; - enum record_type type; + struct record_entry *tmp; if (!record_first.next) return; + /* Loop until a record_end. */ while (1) { - type = record_first.next->type; - - if (type == record_reg) - xfree (record_first.next->u.reg.val); - else if (type == record_mem) - xfree (record_first.next->u.mem.val); + /* Cut record_first.next out of the linked list. */ tmp = record_first.next; record_first.next = tmp->next; - xfree (tmp); + 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; + break; /* End loop when list is empty. */ } - - record_first.next->prev = &record_first; - - if (type == record_end) - break; } - - record_insn_num--; } /* Add a struct record_entry to record_arch_list. */ @@ -236,10 +440,32 @@ record_arch_list_add (struct record_entry *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 num) +record_arch_list_add_reg (struct regcache *regcache, int regnum) { struct record_entry *rec; @@ -247,16 +473,11 @@ record_arch_list_add_reg (struct regcache *regcache, int num) fprintf_unfiltered (gdb_stdlog, "Process record: add register num = %d to " "record list.\n", - num); + regnum); - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_reg; - rec->u.reg.num = num; + rec = record_reg_alloc (regcache, regnum); - regcache_raw_read (regcache, num, rec->u.reg.val); + regcache_raw_read (regcache, regnum, record_get_loc (rec)); record_arch_list_add (rec); @@ -277,27 +498,19 @@ record_arch_list_add_mem (CORE_ADDR addr, int len) "record list.\n", paddress (target_gdbarch, addr), len); - if (!addr) + if (!addr) /* FIXME: Why? Some arch must permit it... */ return 0; - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->u.mem.val = (gdb_byte *) xmalloc (len); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_mem; - rec->u.mem.addr = addr; - rec->u.mem.len = len; - rec->u.mem.mem_entry_not_accessible = 0; + rec = record_mem_alloc (addr, len); - if (target_read_memory (addr, rec->u.mem.val, 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); - xfree (rec->u.mem.val); - xfree (rec); + record_mem_release (rec); return -1; } @@ -317,11 +530,9 @@ record_arch_list_add_end (void) fprintf_unfiltered (gdb_stdlog, "Process record: add end to arch list.\n"); - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_end; + rec = record_end_alloc (); rec->u.end.sigval = TARGET_SIGNAL_0; + rec->u.end.insn_num = ++record_insn_count; record_arch_list_add (rec); @@ -340,6 +551,7 @@ record_check_insn_num (int set_terminal) if (record_stop_at_limit) { int q; + if (set_terminal) target_terminal_ours (); q = yquery (_("Do you want to auto delete previous execution " @@ -350,36 +562,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stopped by user.")); } } } } -/* 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 void -record_message_cleanups (void *ignore) +record_arch_list_cleanups (void *ignore) { record_list_release (record_arch_list_tail); } -struct record_message_args { - struct regcache *regcache; - enum target_signal signal; -}; +/* 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 (void *args) +record_message (struct regcache *regcache, enum target_signal signal) { int ret; - struct record_message_args *myargs = args; - struct gdbarch *gdbarch = get_regcache_arch (myargs->regcache); - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + 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; @@ -412,18 +618,18 @@ record_message (void *args) if (record_list != &record_first) /* FIXME better way to check */ { gdb_assert (record_list->type == record_end); - record_list->u.end.sigval = myargs->signal; + record_list->u.end.sigval = signal; } - if (myargs->signal == TARGET_SIGNAL_0 + if (signal == TARGET_SIGNAL_0 || !gdbarch_process_record_signal_p (gdbarch)) ret = gdbarch_process_record (gdbarch, - myargs->regcache, - regcache_read_pc (myargs->regcache)); + regcache, + regcache_read_pc (regcache)); else ret = gdbarch_process_record_signal (gdbarch, - myargs->regcache, - myargs->signal); + regcache, + signal); if (ret > 0) error (_("Process record: inferior program stopped.")); @@ -444,15 +650,29 @@ record_message (void *args) return 1; } +struct record_message_args { + struct regcache *regcache; + enum target_signal signal; +}; + +static int +record_message_wrapper (void *args) +{ + struct record_message_args *record_args = args; + + return record_message (record_args->regcache, record_args->signal); +} + static int -do_record_message (struct regcache *regcache, - enum target_signal signal) +record_message_wrapper_safe (struct regcache *regcache, + enum target_signal signal) { struct record_message_args args; args.regcache = regcache; args.signal = signal; - return catch_errors (record_message, &args, NULL, RETURN_MASK_ALL); + + return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL); } /* Set to 1 if record_store_registers and record_xfer_partial @@ -472,11 +692,173 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +/* Flag set to TRUE for target_stopped_by_watchpoint. */ +static int record_hw_watchpoint = 0; + +/* Execute one instruction from the record log. Each instruction in + the log will be represented by an arbitrary sequence of register + entries and memory entries, followed by an 'end' entry. */ + +static inline void +record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, + record_get_loc (entry)); + memcpy (record_get_loc (entry), reg, entry->u.reg.len); + } + break; + + case record_mem: /* mem */ + { + /* Nothing to do if the entry is flagged not_accessible. */ + if (!entry->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + entry->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, + record_get_loc (entry), + entry->u.mem.len)) + { + entry->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + memcpy (record_get_loc (entry), mem, entry->u.mem.len); + + /* We've changed memory --- check if a hardware + watchpoint should trap. Note that this + presently assumes the target beneath supports + continuable watchpoints. On non-continuable + watchpoints target, we'll want to check this + _before_ actually doing the memory change, and + not doing the change at all if the watchpoint + traps. */ + if (hardware_watchpoint_inserted_in_range + (get_regcache_aspace (regcache), + entry->u.mem.addr, entry->u.mem.len)) + record_hw_watchpoint = 1; + } + } + } + } + break; + } +} + +static struct target_ops *tmp_to_resume_ops; +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal); +static struct target_ops *tmp_to_wait_ops; +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *, + int); +static struct target_ops *tmp_to_store_registers_ops; +static void (*tmp_to_store_registers) (struct target_ops *, + struct regcache *, + int regno); +static struct target_ops *tmp_to_xfer_partial_ops; +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, + enum target_object object, + const char *annex, + gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, + LONGEST len); +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, + struct bp_target_info *); +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, + struct bp_target_info *); +static int (*tmp_to_stopped_by_watchpoint) (void); +static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *); +static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *); +static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *); + +static void record_restore (void); + +/* Asynchronous signal handle registered as event loop source for when + we have pending events ready to be passed to the core. */ + +static struct async_event_handler *record_async_inferior_event_token; + static void -record_open (char *name, int from_tty) +record_async_inferior_event_handler (gdb_client_data data) { - struct target_ops *t; + 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"); @@ -486,112 +868,256 @@ record_open (char *name, int from_tty) if (non_stop) error (_("Process record target can't debug inferior in non-stop mode " "(non-stop).")); - if (target_async_permitted) - error (_("Process record target can't debug inferior in asynchronous " - "mode (target-async).")); if (!gdbarch_process_record_p (target_gdbarch)) error (_("Process record: the current architecture doesn't support " "record function.")); + if (!tmp_to_resume) + error (_("Could not find 'to_resume' method on the target stack.")); + if (!tmp_to_wait) + error (_("Could not find 'to_wait' method on the target stack.")); + if (!tmp_to_store_registers) + error (_("Could not find 'to_store_registers' " + "method on the target stack.")); + if (!tmp_to_insert_breakpoint) + error (_("Could not find 'to_insert_breakpoint' " + "method on the target stack.")); + if (!tmp_to_remove_breakpoint) + error (_("Could not find 'to_remove_breakpoint' " + "method on the target stack.")); + if (!tmp_to_stopped_by_watchpoint) + error (_("Could not find 'to_stopped_by_watchpoint' " + "method on the target stack.")); + if (!tmp_to_stopped_data_address) + error (_("Could not find 'to_stopped_data_address' " + "method on the target stack.")); + + push_target (&record_ops); +} + +/* "to_open" target method. Open the process record target. */ + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + /* Check if record target is already running. */ if (current_target.to_stratum == record_stratum) error (_("Process record target already running. Use \"record stop\" to " "stop record target first.")); - /*Reset the beneath function pointers. */ - record_beneath_to_resume = NULL; - record_beneath_to_wait = NULL; - record_beneath_to_store_registers = NULL; - record_beneath_to_xfer_partial = NULL; - record_beneath_to_insert_breakpoint = NULL; - record_beneath_to_remove_breakpoint = NULL; + /* 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 (!record_beneath_to_resume) + if (!tmp_to_resume) { - record_beneath_to_resume = t->to_resume; - record_beneath_to_resume_ops = t; + tmp_to_resume = t->to_resume; + tmp_to_resume_ops = t; } - if (!record_beneath_to_wait) + if (!tmp_to_wait) { - record_beneath_to_wait = t->to_wait; - record_beneath_to_wait_ops = t; + tmp_to_wait = t->to_wait; + tmp_to_wait_ops = t; } - if (!record_beneath_to_store_registers) + if (!tmp_to_store_registers) { - record_beneath_to_store_registers = t->to_store_registers; - record_beneath_to_store_registers_ops = t; + tmp_to_store_registers = t->to_store_registers; + tmp_to_store_registers_ops = t; } - if (!record_beneath_to_xfer_partial) + if (!tmp_to_xfer_partial) { - record_beneath_to_xfer_partial = t->to_xfer_partial; - record_beneath_to_xfer_partial_ops = t; + tmp_to_xfer_partial = t->to_xfer_partial; + tmp_to_xfer_partial_ops = t; } - if (!record_beneath_to_insert_breakpoint) - record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; - if (!record_beneath_to_remove_breakpoint) - record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; + if (!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 (!record_beneath_to_resume) - error (_("Process record can't get to_resume.")); - if (!record_beneath_to_wait) - error (_("Process record can't get to_wait.")); - if (!record_beneath_to_store_registers) - error (_("Process record can't get to_store_registers.")); - if (!record_beneath_to_xfer_partial) - error (_("Process record can't get to_xfer_partial.")); - if (!record_beneath_to_insert_breakpoint) - error (_("Process record can't get to_insert_breakpoint.")); - if (!record_beneath_to_remove_breakpoint) - error (_("Process record can't get to_remove_breakpoint.")); - - push_target (&record_ops); + if (!tmp_to_xfer_partial) + error (_("Could not find 'to_xfer_partial' method on the target stack.")); /* Reset */ record_insn_num = 0; + record_insn_count = 0; record_list = &record_first; record_list->next = NULL; + + /* Set the tmp beneath pointers to beneath pointers. */ + record_beneath_to_resume_ops = tmp_to_resume_ops; + record_beneath_to_resume = tmp_to_resume; + record_beneath_to_wait_ops = tmp_to_wait_ops; + record_beneath_to_wait = tmp_to_wait; + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; + record_beneath_to_store_registers = tmp_to_store_registers; + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; + record_beneath_to_xfer_partial = tmp_to_xfer_partial; + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; + record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint; + record_beneath_to_stopped_data_address = tmp_to_stopped_data_address; + record_beneath_to_async = tmp_to_async; + + if (core_bfd) + record_core_open_1 (name, from_tty); + else + record_open_1 (name, from_tty); + + /* Register extra event sources in the event loop. */ + record_async_inferior_event_token + = create_async_event_handler (record_async_inferior_event_handler, + NULL); } +/* "to_close" target method. Close the process record target. */ + static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + if (record_core_regbuf) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + } + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } + + if (record_async_inferior_event_token) + delete_async_event_handler (&record_async_inferior_event_token); } static int record_resume_step = 0; -static int record_resume_error; + +/* True if we've been resumed, and so each record_wait call should + advance execution. If this is false, record_wait will return a + TARGET_WAITKIND_IGNORE. */ +static int record_resumed = 0; + +/* The execution direction of the last resume we got. This is + necessary for async mode. Vis (order is not strictly accurate): + + 1. user has the global execution direction set to forward + 2. user does a reverse-step command + 3. record_resume is called with global execution direction + temporarily switched to reverse + 4. GDB's execution direction is reverted back to forward + 5. target record notifies event loop there's an event to handle + 6. infrun asks the target which direction was it going, and switches + the global execution direction accordingly (to reverse) + 7. infrun polls an event out of the record target, and handles it + 8. GDB goes back to the event loop, and goto #4. +*/ +static enum exec_direction_kind record_execution_dir = EXEC_FORWARD; + +/* "to_resume" target method. Resume the process record target. */ static void record_resume (struct target_ops *ops, ptid_t ptid, int step, enum target_signal signal) { record_resume_step = step; + record_resumed = 1; + record_execution_dir = execution_direction; if (!RECORD_IS_REPLAY) { - if (do_record_message (get_current_regcache (), signal)) - { - record_resume_error = 0; - } - else + struct gdbarch *gdbarch = target_thread_architecture (ptid); + + record_message (get_current_regcache (), signal); + + if (!step) { - record_resume_error = 1; - return; + /* 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, 1, - signal); + + 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) { @@ -618,32 +1144,47 @@ record_wait_cleanups (void *ignore) record_list = record_list->prev; } -/* In replay mode, this function examines the recorded log and - determines where to stop. */ +/* "to_wait" target method for process record target. + + In record mode, the target is always run in singlestep mode + (even when gdb says to continue). The to_wait method intercepts + the stop events and determines which ones are to be passed on to + gdb. Most stop events are just singlestep events that gdb is not + to know about, so the to_wait method just records them and keeps + singlestepping. + + In replay mode, this function emulates the recorded execution log, + one instruction at a time (forward or backward), and determines + where to stop. */ static ptid_t -record_wait (struct target_ops *ops, - ptid_t ptid, struct target_waitstatus *status, - int options) +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\n", - record_resume_step); + "record_resume_step = %d, record_resumed = %d, direction=%s\n", + record_resume_step, record_resumed, + record_execution_dir == EXEC_FORWARD ? "forward" : "reverse"); - if (!RECORD_IS_REPLAY) + if (!record_resumed) { - if (record_resume_error) - { - /* If record_resume get error, return directly. */ - status->kind = TARGET_WAITKIND_STOPPED; - status->value.sig = TARGET_SIGNAL_ABRT; - return inferior_ptid; - } + 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. */ @@ -655,44 +1196,96 @@ record_wait (struct target_ops *ops, /* This is not a single step. */ ptid_t ret; CORE_ADDR tmp_pc; + struct gdbarch *gdbarch = target_thread_architecture (inferior_ptid); while (1) { ret = record_beneath_to_wait (record_beneath_to_wait_ops, ptid, status, options); + if (status->kind == TARGET_WAITKIND_IGNORE) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_wait " + "target beneath not done yet\n"); + return ret; + } + + if (single_step_breakpoints_inserted ()) + remove_single_step_breakpoints (); + + if (record_resume_step) + return ret; /* Is this a SIGTRAP? */ if (status->kind == TARGET_WAITKIND_STOPPED && status->value.sig == TARGET_SIGNAL_TRAP) { - /* Yes -- check if there is a breakpoint. */ + 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 (); - tmp_pc = regcache_read_pc (get_current_regcache ()); - if (breakpoint_inserted_here_p (tmp_pc)) + regcache = get_current_regcache (); + tmp_pc = regcache_read_pc (regcache); + aspace = get_regcache_aspace (regcache); + + if (target_stopped_by_watchpoint ()) { - /* There is a breakpoint. GDB will want to stop. */ - CORE_ADDR decr_pc_after_break = - gdbarch_decr_pc_after_break - (get_regcache_arch (get_current_regcache ())); - if (decr_pc_after_break) + /* 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)) { - regcache_write_pc (get_thread_regcache (ret), - tmp_pc + decr_pc_after_break); + struct gdbarch *gdbarch + = get_regcache_arch (regcache); + CORE_ADDR decr_pc_after_break + = gdbarch_decr_pc_after_break (gdbarch); + if (decr_pc_after_break) + regcache_write_pc (regcache, + tmp_pc + decr_pc_after_break); } } else { - /* There is not a breakpoint, and gdb is not - stepping, therefore gdb will not stop. - Therefore we will not return to gdb. - Record the insn and resume. */ - if (!do_record_message (get_current_regcache (), - TARGET_SIGNAL_0)) + /* This is a single-step trap. Record the + insn and issue another step. + FIXME: this part can be a random SIGTRAP too. + But GDB cannot handle it. */ + int step = 1; + + if (!record_message_wrapper_safe (regcache, + TARGET_SIGNAL_0)) + { + status->kind = TARGET_WAITKIND_STOPPED; + status->value.sig = TARGET_SIGNAL_0; + break; + } + + if (gdbarch_software_single_step_p (gdbarch)) { - break; + /* 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, 1, + ptid, step, TARGET_SIGNAL_0); continue; } @@ -709,34 +1302,37 @@ record_wait (struct target_ops *ops, { 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 (tmp_pc)) + 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 (gdbarch_decr_pc_after_break (gdbarch) - && !record_resume_step) + + if (decr_pc_after_break + && !record_resume_step + && software_breakpoint_inserted_here_p (aspace, tmp_pc)) regcache_write_pc (regcache, - tmp_pc + - gdbarch_decr_pc_after_break (gdbarch)); + tmp_pc + decr_pc_after_break); goto replay_out; } } - record_get_sig = 0; - signal (SIGINT, record_sig_handler); /* If GDB is in terminal_inferior mode, it will not get the signal. And in GDB replay mode, GDB doesn't need to be in terminal_inferior mode, because inferior will not executed. @@ -767,76 +1363,9 @@ record_wait (struct target_ops *ops, break; } - /* Set ptid, register and memory according to record_list. */ - if (record_list->type == record_reg) - { - /* reg */ - gdb_byte reg[MAX_REGISTER_SIZE]; - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_reg %s to " - "inferior num = %d.\n", - host_address_to_string (record_list), - record_list->u.reg.num); - regcache_cooked_read (regcache, record_list->u.reg.num, reg); - regcache_cooked_write (regcache, record_list->u.reg.num, - record_list->u.reg.val); - memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); - } - else if (record_list->type == record_mem) - { - /* mem */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - gdb_byte *mem = alloca (record_list->u.mem.len); - if (record_debug > 1) - fprintf_unfiltered (gdb_stdlog, - "Process record: record_mem %s to " - "inferior addr = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); - - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + record_exec_insn (regcache, gdbarch, record_list); + + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -867,19 +1396,32 @@ record_wait (struct target_ops *ops, /* check breakpoint */ tmp_pc = regcache_read_pc (regcache); - if (breakpoint_inserted_here_p (tmp_pc)) + 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 (gdbarch_decr_pc_after_break (gdbarch) + if (decr_pc_after_break && execution_direction == EXEC_FORWARD - && !record_resume_step) + && !record_resume_step + && software_breakpoint_inserted_here_p (aspace, + tmp_pc)) regcache_write_pc (regcache, - tmp_pc + - gdbarch_decr_pc_after_break (gdbarch)); + 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 */ @@ -905,8 +1447,6 @@ record_wait (struct target_ops *ops, } while (continue_flag); - signal (SIGINT, handle_sigint); - replay_out: if (record_get_sig) status->value.sig = TARGET_SIGNAL_INT; @@ -919,13 +1459,53 @@ replay_out: discard_cleanups (old_cleanups); } + signal (SIGINT, handle_sigint); + do_cleanups (set_cleanups); return inferior_ptid; } -static void -record_disconnect (struct target_ops *target, char *args, int from_tty) -{ +static ptid_t +record_wait (struct target_ops *ops, + ptid_t ptid, struct target_waitstatus *status, + int options) +{ + ptid_t return_ptid; + + return_ptid = record_wait_1 (ops, ptid, status, options); + if (status->kind != TARGET_WAITKIND_IGNORE) + { + /* We're reporting a stop. Make sure any spurious + target_wait(WNOHANG) doesn't advance the target until the + core wants us resumed again. */ + record_resumed = 0; + } + return return_ptid; +} + +static int +record_stopped_by_watchpoint (void) +{ + if (RECORD_IS_REPLAY) + return record_hw_watchpoint; + else + return record_beneath_to_stopped_by_watchpoint (); +} + +static int +record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p) +{ + if (RECORD_IS_REPLAY) + return 0; + else + return record_beneath_to_stopped_data_address (ops, addr_p); +} + +/* "to_disconnect" method for process record target. */ + +static void +record_disconnect (struct target_ops *target, char *args, int from_tty) +{ if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); @@ -933,6 +1513,8 @@ record_disconnect (struct target_ops *target, char *args, int from_tty) 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) { @@ -943,6 +1525,8 @@ record_detach (struct target_ops *ops, char *args, int from_tty) target_detach (args, from_tty); } +/* "to_mourn_inferior" method for process record target. */ + static void record_mourn_inferior (struct target_ops *ops) { @@ -980,6 +1564,7 @@ record_registers_change (struct regcache *regcache, int regnum) 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)) @@ -1012,6 +1597,8 @@ record_registers_change (struct regcache *regcache, int regnum) 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) @@ -1044,6 +1631,7 @@ record_store_registers (struct target_ops *ops, struct regcache *regcache, if (regno < 0) { int i; + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) @@ -1056,7 +1644,7 @@ record_store_registers (struct target_ops *ops, struct regcache *regcache, } /* Destroy the record from here forward. */ - record_list_release_next (); + record_list_release_following (record_list); } record_registers_change (regcache, regno); @@ -1065,7 +1653,7 @@ record_store_registers (struct target_ops *ops, struct regcache *regcache, regcache, regno); } -/* Behavior is conditional on RECORD_IS_REPLAY. +/* "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. */ @@ -1088,7 +1676,7 @@ record_xfer_partial (struct target_ops *ops, enum target_object object, error (_("Process record canceled the operation.")); /* Destroy the record from here forward. */ - record_list_release_next (); + record_list_release_following (record_list); } /* Check record_insn_num */ @@ -1102,8 +1690,8 @@ record_xfer_partial (struct target_ops *ops, enum target_object object, record_list_release (record_arch_list_tail); if (record_debug) fprintf_unfiltered (gdb_stdlog, - _("Process record: failed to record " - "execution log.")); + "Process record: failed to record " + "execution log."); return -1; } if (record_arch_list_add_end ()) @@ -1111,8 +1699,8 @@ record_xfer_partial (struct target_ops *ops, enum target_object object, record_list_release (record_arch_list_tail); if (record_debug) fprintf_unfiltered (gdb_stdlog, - _("Process record: failed to record " - "execution log.")); + "Process record: failed to record " + "execution log."); return -1; } record_list->next = record_arch_list_head; @@ -1151,6 +1739,8 @@ record_insert_breakpoint (struct gdbarch *gdbarch, return 0; } +/* "to_remove_breakpoint" method for process record target. */ + static int record_remove_breakpoint (struct gdbarch *gdbarch, struct bp_target_info *bp_tgt) @@ -1168,12 +1758,96 @@ record_remove_breakpoint (struct gdbarch *gdbarch, return 0; } +/* "to_can_execute_reverse" method for process record target. */ + static int record_can_execute_reverse (void) { return 1; } +/* "to_get_bookmark" method for process record and prec over core. */ + +static gdb_byte * +record_get_bookmark (char *args, int from_tty) +{ + gdb_byte *ret = NULL; + + /* Return stringified form of instruction count. */ + if (record_list && record_list->type == record_end) + ret = xstrdup (pulongest (record_list->u.end.insn_num)); + + if (record_debug) + { + if (ret) + fprintf_unfiltered (gdb_stdlog, + "record_get_bookmark returns %s\n", ret); + else + fprintf_unfiltered (gdb_stdlog, + "record_get_bookmark returns NULL\n"); + } + return ret; +} + +/* The implementation of the command "record goto". */ +static void cmd_record_goto (char *, int); + +/* "to_goto_bookmark" method for process record and prec over core. */ + +static void +record_goto_bookmark (gdb_byte *bookmark, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "record_goto_bookmark receives %s\n", bookmark); + + if (bookmark[0] == '\'' || bookmark[0] == '\"') + { + if (bookmark[strlen (bookmark) - 1] != bookmark[0]) + error (_("Unbalanced quotes: %s"), bookmark); + + /* Strip trailing quote. */ + bookmark[strlen (bookmark) - 1] = '\0'; + /* Strip leading quote. */ + bookmark++; + /* Pass along to cmd_record_goto. */ + } + + cmd_record_goto ((char *) bookmark, from_tty); + return; +} + +static void +record_async (void (*callback) (enum inferior_event_type event_type, + void *context), void *context) +{ + /* If we're on top of a line target (e.g., linux-nat, remote), then + set it to async mode as well. Will be NULL if we're sitting on + top of the core target, for "record restore". */ + if (record_beneath_to_async != NULL) + record_beneath_to_async (callback, context); +} + +static int +record_can_async_p (void) +{ + /* We only enable async when the user specifically asks for it. */ + return target_async_permitted; +} + +static int +record_is_async_p (void) +{ + /* We only enable async when the user specifically asks for it. */ + return target_async_permitted; +} + +static enum exec_direction_kind +record_execution_direction (void) +{ + return record_execution_dir; +} + static void init_record_ops (void) { @@ -1194,11 +1868,247 @@ init_record_ops (void) record_ops.to_xfer_partial = record_xfer_partial; record_ops.to_insert_breakpoint = record_insert_breakpoint; record_ops.to_remove_breakpoint = record_remove_breakpoint; + record_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint; + record_ops.to_stopped_data_address = record_stopped_data_address; record_ops.to_can_execute_reverse = record_can_execute_reverse; record_ops.to_stratum = record_stratum; + /* Add bookmark target methods. */ + record_ops.to_get_bookmark = record_get_bookmark; + record_ops.to_goto_bookmark = record_goto_bookmark; + record_ops.to_async = record_async; + record_ops.to_can_async_p = record_can_async_p; + record_ops.to_is_async_p = record_is_async_p; + record_ops.to_execution_direction = record_execution_direction; record_ops.to_magic = OPS_MAGIC; } +/* "to_resume" method for prec over corefile. */ + +static void +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal signal) +{ + record_resume_step = step; + record_resumed = 1; + record_execution_dir = execution_direction; + + /* We are about to start executing the inferior (or simulate it), + let's register it with the event loop. */ + if (target_can_async_p ()) + { + target_async (inferior_event_handler, 0); + + /* Notify the event loop there's an event to wait for. */ + mark_async_event_handler (record_async_inferior_event_token); + } +} + +/* "to_kill" method for prec over corefile. */ + +static void +record_core_kill (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); + + unpush_target (&record_core_ops); +} + +/* "to_fetch_registers" method for prec over corefile. */ + +static void +record_core_fetch_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); +} + +/* "to_prepare_to_store" method for prec over corefile. */ + +static void +record_core_prepare_to_store (struct regcache *regcache) +{ +} + +/* "to_store_registers" method for prec over corefile. */ + +static void +record_core_store_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); +} + +/* "to_xfer_partial" method for prec over corefile. */ + +static LONGEST +record_core_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, ULONGEST offset, + LONGEST len) +{ + if (object == TARGET_OBJECT_MEMORY) + { + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + ULONGEST sec_offset; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + sec_offset = offset - p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry = (struct record_core_buf_entry *) + xmalloc (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + sec_offset, writebuf, + (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + sec_offset, + (size_t) len); + } + + return len; + } + } + + return -1; + } + else + error (_("You can't do that without a process to debug.")); + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +/* "to_insert_breakpoint" method for prec over corefile. */ + +static int +record_core_insert_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +/* "to_remove_breakpoint" method for prec over corefile. */ + +static int +record_core_remove_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +/* "to_has_execution" method for prec over corefile. */ + +static int +record_core_has_execution (struct target_ops *ops, ptid_t the_ptid) +{ + return 1; +} + +static void +init_record_core_ops (void) +{ + record_core_ops.to_shortname = "record-core"; + record_core_ops.to_longname = "Process record and replay target"; + record_core_ops.to_doc = + "Log program while executing and replay execution from log."; + record_core_ops.to_open = record_open; + record_core_ops.to_close = record_close; + record_core_ops.to_resume = record_core_resume; + record_core_ops.to_wait = record_wait; + record_core_ops.to_kill = record_core_kill; + record_core_ops.to_fetch_registers = record_core_fetch_registers; + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; + record_core_ops.to_store_registers = record_core_store_registers; + record_core_ops.to_xfer_partial = record_core_xfer_partial; + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; + record_core_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint; + record_core_ops.to_stopped_data_address = record_stopped_data_address; + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; + record_core_ops.to_has_execution = record_core_has_execution; + record_core_ops.to_stratum = record_stratum; + /* Add bookmark target methods. */ + record_core_ops.to_get_bookmark = record_get_bookmark; + record_core_ops.to_goto_bookmark = record_goto_bookmark; + record_core_ops.to_async = record_async; + record_core_ops.to_can_async_p = record_can_async_p; + record_core_ops.to_is_async_p = record_is_async_p; + record_core_ops.to_execution_direction = record_execution_direction; + record_core_ops.to_magic = OPS_MAGIC; +} + +/* Implement "show record debug" command. */ + static void show_record_debug (struct ui_file *file, int from_tty, struct cmd_list_element *c, const char *value) @@ -1228,7 +2138,7 @@ cmd_record_delete (char *args, int from_tty) if (!from_tty || query (_("Delete the log from this point forward " "and begin to record the running message " "at current PC?"))) - record_list_release_next (); + record_list_release_following (record_list); } else printf_unfiltered (_("Already at end of record list.\n")); @@ -1238,7 +2148,7 @@ cmd_record_delete (char *args, int from_tty) printf_unfiltered (_("Process record is not started.\n")); } -/* Implement the "stoprecord" command. */ +/* Implement the "stoprecord" or "record stop" command. */ static void cmd_record_stop (char *args, int from_tty) @@ -1246,8 +2156,8 @@ cmd_record_stop (char *args, int from_tty) if (current_target.to_stratum == record_stratum) { unpush_target (&record_ops); - printf_unfiltered (_("Process record is stoped and all execution " - "log is deleted.\n")); + printf_unfiltered (_("Process record is stopped and all execution " + "logs are deleted.\n")); } else printf_unfiltered (_("Process record is not started.\n")); @@ -1260,33 +2170,23 @@ 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) { - printf_unfiltered (_("Record instructions number is bigger than " - "record instructions max number. Auto delete " - "the first ones?\n")); - + /* Count down record_insn_num while releasing records from list. */ while (record_insn_num > record_insn_max_num) - record_list_release_first (); + { + record_list_release_first (); + record_insn_num--; + } } } -/* Print the current index into the record log (number of insns recorded - so far). */ - -static void -show_record_insn_number (char *ignore, int from_tty) -{ - printf_unfiltered (_("Record instruction number is %d.\n"), - record_insn_num); -} - static struct cmd_list_element *record_cmdlist, *set_record_cmdlist, *show_record_cmdlist, *info_record_cmdlist; static void set_record_command (char *args, int from_tty) { - printf_unfiltered (_("\ -\"set record\" must be followed by an apporpriate subcommand.\n")); + printf_unfiltered (_("\"set record\" must be followed " + "by an apporpriate subcommand.\n")); help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout); } @@ -1296,15 +2196,677 @@ show_record_command (char *args, int from_tty) cmd_show_list (show_record_cmdlist, from_tty, ""); } +/* Display some statistics about the execution log. */ + static void info_record_command (char *args, int from_tty) { - cmd_show_list (info_record_cmdlist, from_tty, ""); + struct record_entry *p; + + if (current_target.to_stratum == record_stratum) + { + if (RECORD_IS_REPLAY) + printf_filtered (_("Replay mode:\n")); + else + printf_filtered (_("Record mode:\n")); + + /* Find entry for first actual instruction in the log. */ + for (p = record_first.next; + p != NULL && p->type != record_end; + p = p->next) + ; + + /* Do we have a log at all? */ + if (p != NULL && p->type == record_end) + { + /* Display instruction number for first instruction in the log. */ + printf_filtered (_("Lowest recorded instruction number is %s.\n"), + pulongest (p->u.end.insn_num)); + + /* If in replay mode, display where we are in the log. */ + if (RECORD_IS_REPLAY) + printf_filtered (_("Current instruction number is %s.\n"), + pulongest (record_list->u.end.insn_num)); + + /* Display instruction number for last instruction in the log. */ + printf_filtered (_("Highest recorded instruction number is %s.\n"), + pulongest (record_insn_count)); + + /* Display log count. */ + printf_filtered (_("Log contains %d instructions.\n"), + record_insn_num); + } + else + { + printf_filtered (_("No instructions have been logged.\n")); + } + } + else + { + printf_filtered (_("target record is not active.\n")); + } + + /* Display max log size. */ + printf_filtered (_("Max logged instructions is %d.\n"), + record_insn_max_num); +} + +/* Record log save-file format + Version 1 (never released) + + Header: + 4 bytes: magic number htonl(0x20090829). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end, see enum record_type). + record_reg: + 1 byte: record type (record_reg, see enum record_type). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem, see enum record_type). + 8 bytes: memory length (network byte order). + 8 bytes: memory address (network byte order). + n bytes: memory value (n == memory length). + + Version 2 + 4 bytes: magic number netorder32(0x20091016). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end, see enum record_type). + 4 bytes: signal + 4 bytes: instruction count + record_reg: + 1 byte: record type (record_reg, see enum record_type). + 4 bytes: register id (network byte order). + n bytes: register value (n == actual register size). + (eg. 4 bytes for x86 general registers). + record_mem: + 1 byte: record type (record_mem, see enum record_type). + 4 bytes: memory length (network byte order). + 8 bytes: memory address (network byte order). + n bytes: memory value (n == memory length). + +*/ + +/* bfdcore_read -- read bytes from a core file section. */ + +static inline void +bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset) +{ + int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len); + + if (ret) + *offset += len; + else + error (_("Failed to read %d bytes from core file %s ('%s')."), + len, bfd_get_filename (obfd), + bfd_errmsg (bfd_get_error ())); +} + +static inline uint64_t +netorder64 (uint64_t input) +{ + uint64_t ret; + + store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), + BFD_ENDIAN_BIG, input); + return ret; +} + +static inline uint32_t +netorder32 (uint32_t input) +{ + uint32_t ret; + + store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), + BFD_ENDIAN_BIG, input); + return ret; +} + +static inline uint16_t +netorder16 (uint16_t input) +{ + uint16_t ret; + + store_unsigned_integer ((gdb_byte *) &ret, sizeof (ret), + BFD_ENDIAN_BIG, input); + return ret; +} + +/* Restore the execution log from a core_bfd file. */ +static void +record_restore (void) +{ + uint32_t magic; + struct cleanup *old_cleanups; + struct record_entry *rec; + asection *osec; + uint32_t osec_size; + int bfd_offset = 0; + struct regcache *regcache; + + /* We restore the execution log from the open core bfd, + if there is one. */ + if (core_bfd == NULL) + return; + + /* "record_restore" can only be called when record list is empty. */ + gdb_assert (record_first.next == NULL); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Restoring recording from core file.\n"); + + /* Now need to find our special note section. */ + osec = bfd_get_section_by_name (core_bfd, "null0"); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n", + osec ? "succeeded" : "failed"); + if (osec == NULL) + return; + osec_size = bfd_section_size (core_bfd, osec); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec)); + + /* Check the magic code. */ + bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset); + if (magic != RECORD_FILE_MAGIC) + error (_("Version mis-match or file format error in core file %s."), + bfd_get_filename (core_bfd)); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + " Reading 4-byte magic cookie " + "RECORD_FILE_MAGIC (0x%s)\n", + phex_nz (netorder32 (magic), 4)); + + /* Restore the entries in recfd into record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + record_insn_num = 0; + old_cleanups = make_cleanup (record_arch_list_cleanups, 0); + regcache = get_current_regcache (); + + while (1) + { + uint8_t rectype; + uint32_t regnum, len, signal, count; + uint64_t addr; + + /* We are finished when offset reaches osec_size. */ + if (bfd_offset >= osec_size) + break; + bfdcore_read (core_bfd, osec, &rectype, sizeof (rectype), &bfd_offset); + + switch (rectype) + { + case record_reg: /* reg */ + /* Get register number to regnum. */ + bfdcore_read (core_bfd, osec, ®num, + sizeof (regnum), &bfd_offset); + regnum = netorder32 (regnum); + + rec = record_reg_alloc (regcache, regnum); + + /* Get val. */ + bfdcore_read (core_bfd, osec, record_get_loc (rec), + rec->u.reg.len, &bfd_offset); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + " Reading register %d (1 " + "plus %lu plus %d bytes)\n", + rec->u.reg.num, + (unsigned long) sizeof (regnum), + rec->u.reg.len); + break; + + case record_mem: /* mem */ + /* Get len. */ + bfdcore_read (core_bfd, osec, &len, + sizeof (len), &bfd_offset); + len = netorder32 (len); + + /* Get addr. */ + bfdcore_read (core_bfd, osec, &addr, + sizeof (addr), &bfd_offset); + addr = netorder64 (addr); + + rec = record_mem_alloc (addr, len); + + /* Get val. */ + bfdcore_read (core_bfd, osec, record_get_loc (rec), + rec->u.mem.len, &bfd_offset); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + " Reading memory %s (1 plus " + "%lu plus %lu plus %d bytes)\n", + paddress (get_current_arch (), + rec->u.mem.addr), + (unsigned long) sizeof (addr), + (unsigned long) sizeof (len), + rec->u.mem.len); + break; + + case record_end: /* end */ + rec = record_end_alloc (); + record_insn_num ++; + + /* Get signal value. */ + bfdcore_read (core_bfd, osec, &signal, + sizeof (signal), &bfd_offset); + signal = netorder32 (signal); + rec->u.end.sigval = signal; + + /* Get insn count. */ + bfdcore_read (core_bfd, osec, &count, + sizeof (count), &bfd_offset); + count = netorder32 (count); + rec->u.end.insn_num = count; + record_insn_count = count + 1; + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + " Reading record_end (1 + " + "%lu + %lu bytes), offset == %s\n", + (unsigned long) sizeof (signal), + (unsigned long) sizeof (count), + paddress (get_current_arch (), + bfd_offset)); + break; + + default: + error (_("Bad entry type in core file %s."), + bfd_get_filename (core_bfd)); + break; + } + + /* Add rec to record arch list. */ + record_arch_list_add (rec); + } + + discard_cleanups (old_cleanups); + + /* Add record_arch_list_head to the end of record list. */ + record_first.next = record_arch_list_head; + record_arch_list_head->prev = &record_first; + record_arch_list_tail->next = NULL; + record_list = &record_first; + + /* Update record_insn_max_num. */ + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + /* Succeeded. */ + printf_filtered (_("Restored records from core file %s.\n"), + bfd_get_filename (core_bfd)); + + print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); +} + +/* bfdcore_write -- write bytes into a core file section. */ + +static inline void +bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset) +{ + int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len); + + if (ret) + *offset += len; + else + error (_("Failed to write %d bytes to core file %s ('%s')."), + len, bfd_get_filename (obfd), + bfd_errmsg (bfd_get_error ())); +} + +/* Restore the execution log from a file. We use a modified elf + corefile format, with an extra section for our data. */ + +static void +cmd_record_restore (char *args, int from_tty) +{ + core_file_command (args, from_tty); + record_open (args, from_tty); +} + +static void +record_save_cleanups (void *data) +{ + bfd *obfd = data; + char *pathname = xstrdup (bfd_get_filename (obfd)); + + bfd_close (obfd); + unlink (pathname); + xfree (pathname); +} + +/* Save the execution log to a file. We use a modified elf corefile + format, with an extra section for our data. */ + +static void +cmd_record_save (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + bfd *obfd; + int save_size = 0; + asection *osec = NULL; + int bfd_offset = 0; + + if (strcmp (current_target.to_shortname, "record") != 0) + error (_("This command can only be used with target 'record'.\n" + "Use 'target record' first.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default recfile name is "gdb_record.PID". */ + snprintf (recfilename_buffer, sizeof (recfilename_buffer), + "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the save file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Saving execution log to core file '%s'\n", + recfilename); + + /* Open the output file. */ + obfd = create_gcore_bfd (recfilename); + old_cleanups = make_cleanup (record_save_cleanups, obfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_insn (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Compute the size needed for the extra bfd section. */ + save_size = 4; /* magic cookie */ + for (record_list = record_first.next; record_list; + record_list = record_list->next) + switch (record_list->type) + { + case record_end: + save_size += 1 + 4 + 4; + break; + case record_reg: + save_size += 1 + 4 + record_list->u.reg.len; + break; + case record_mem: + save_size += 1 + 4 + 8 + record_list->u.mem.len; + break; + } + + /* Make the new bfd section. */ + osec = bfd_make_section_anyway_with_flags (obfd, "precord", + SEC_HAS_CONTENTS + | SEC_READONLY); + if (osec == NULL) + error (_("Failed to create 'precord' section for corefile %s: %s"), + recfilename, + bfd_errmsg (bfd_get_error ())); + bfd_set_section_size (obfd, osec, save_size); + bfd_set_section_vma (obfd, osec, 0); + bfd_set_section_alignment (obfd, osec, 0); + bfd_section_lma (obfd, osec) = 0; + + /* Save corefile state. */ + write_gcore_file (obfd); + + /* Write out the record log. */ + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + " Writing 4-byte magic cookie " + "RECORD_FILE_MAGIC (0x%s)\n", + phex_nz (magic, 4)); + bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset); + + /* Save the entries to recfd and forward execute to the end of + record list. */ + record_list = &record_first; + while (1) + { + /* Save entry. */ + if (record_list != &record_first) + { + uint8_t type; + uint32_t regnum, len, signal, count; + uint64_t addr; + + type = record_list->type; + bfdcore_write (obfd, osec, &type, sizeof (type), &bfd_offset); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + " Writing register %d (1 " + "plus %lu plus %d bytes)\n", + record_list->u.reg.num, + (unsigned long) sizeof (regnum), + record_list->u.reg.len); + + /* Write regnum. */ + regnum = netorder32 (record_list->u.reg.num); + bfdcore_write (obfd, osec, ®num, + sizeof (regnum), &bfd_offset); + + /* Write regval. */ + bfdcore_write (obfd, osec, record_get_loc (record_list), + record_list->u.reg.len, &bfd_offset); + break; + + case record_mem: /* mem */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + " Writing memory %s (1 plus " + "%lu plus %lu plus %d bytes)\n", + paddress (gdbarch, + record_list->u.mem.addr), + (unsigned long) sizeof (addr), + (unsigned long) sizeof (len), + record_list->u.mem.len); + + /* Write memlen. */ + len = netorder32 (record_list->u.mem.len); + bfdcore_write (obfd, osec, &len, sizeof (len), &bfd_offset); + + /* Write memaddr. */ + addr = netorder64 (record_list->u.mem.addr); + bfdcore_write (obfd, osec, &addr, + sizeof (addr), &bfd_offset); + + /* Write memval. */ + bfdcore_write (obfd, osec, record_get_loc (record_list), + record_list->u.mem.len, &bfd_offset); + break; + + case record_end: + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + " Writing record_end (1 + " + "%lu + %lu bytes)\n", + (unsigned long) sizeof (signal), + (unsigned long) sizeof (count)); + /* Write signal value. */ + signal = netorder32 (record_list->u.end.sigval); + bfdcore_write (obfd, osec, &signal, + sizeof (signal), &bfd_offset); + + /* Write insn count. */ + count = netorder32 (record_list->u.end.insn_num); + bfdcore_write (obfd, osec, &count, + sizeof (count), &bfd_offset); + break; + } + } + + /* Execute entry. */ + record_exec_insn (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_insn (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + bfd_close (obfd); + discard_cleanups (old_cleanups); + + /* Succeeded. */ + printf_filtered (_("Saved core file %s with execution log.\n"), + recfilename); +} + +/* record_goto_insn -- rewind the record log (forward or backward, + depending on DIR) to the given entry, changing the program state + correspondingly. */ + +static void +record_goto_insn (struct record_entry *entry, + enum exec_direction_kind dir) +{ + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); + struct regcache *regcache = get_current_regcache (); + struct gdbarch *gdbarch = get_regcache_arch (regcache); + + /* Assume everything is valid: we will hit the entry, + and we will not hit the end of the recording. */ + + if (dir == EXEC_FORWARD) + record_list = record_list->next; + + do + { + record_exec_insn (regcache, gdbarch, record_list); + if (dir == EXEC_REVERSE) + record_list = record_list->prev; + else + record_list = record_list->next; + } while (record_list != entry); + do_cleanups (set_cleanups); +} + +/* "record goto" command. Argument is an instruction number, + as given by "info record". + + Rewinds the recording (forward or backward) to the given instruction. */ + +static void +cmd_record_goto (char *arg, int from_tty) +{ + struct record_entry *p = NULL; + ULONGEST target_insn = 0; + + if (arg == NULL || *arg == '\0') + error (_("Command requires an argument (insn number to go to).")); + + if (strncmp (arg, "start", strlen ("start")) == 0 + || strncmp (arg, "begin", strlen ("begin")) == 0) + { + /* Special case. Find first insn. */ + for (p = &record_first; p != NULL; p = p->next) + if (p->type == record_end) + break; + if (p) + target_insn = p->u.end.insn_num; + } + else if (strncmp (arg, "end", strlen ("end")) == 0) + { + /* Special case. Find last insn. */ + for (p = record_list; p->next != NULL; p = p->next) + ; + for (; p!= NULL; p = p->prev) + if (p->type == record_end) + break; + if (p) + target_insn = p->u.end.insn_num; + } + else + { + /* General case. Find designated insn. */ + target_insn = parse_and_eval_long (arg); + + for (p = &record_first; p != NULL; p = p->next) + if (p->type == record_end && p->u.end.insn_num == target_insn) + break; + } + + if (p == NULL) + error (_("Target insn '%s' not found."), arg); + else if (p == record_list) + error (_("Already at insn '%s'."), arg); + else if (p->u.end.insn_num > record_list->u.end.insn_num) + { + printf_filtered (_("Go forward to insn number %s\n"), + pulongest (target_insn)); + record_goto_insn (p, EXEC_FORWARD); + } + else + { + printf_filtered (_("Go backward to insn number %s\n"), + pulongest (target_insn)); + record_goto_insn (p, EXEC_REVERSE); + } + registers_changed (); + reinit_frame_cache (); + print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); } void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1312,6 +2874,8 @@ _initialize_record (void) 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."), @@ -1321,9 +2885,11 @@ _initialize_record (void) NULL, show_record_debug, &setdebuglist, &showdebuglist); - add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), - &record_cmdlist, "record ", 0, &cmdlist); + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command."), + &record_cmdlist, "record ", 0, &cmdlist); + set_cmd_completer (c, filename_completer); + add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, @@ -1338,6 +2904,18 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("save", class_obscure, cmd_record_save, + _("Save the execution log to a file.\n\ +Argument is optional filename.\n\ +Default filename is 'gdb_record.'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + + 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."), @@ -1354,14 +2932,14 @@ _initialize_record (void) 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\ +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_zinteger_cmd ("insn-number-max", no_class, + add_setshow_uinteger_cmd ("insn-number-max", no_class, &record_insn_max_num, _("Set record/replay buffer limit."), _("Show record/replay buffer limit."), _("\ @@ -1369,7 +2947,21 @@ Set the maximum number of instructions to be stored in the\n\ record/replay buffer. Zero means unlimited. Default is 200000."), set_record_insn_max_num, NULL, &set_record_cmdlist, &show_record_cmdlist); - add_cmd ("insn-number", class_obscure, show_record_insn_number, - _("Show the current number of instructions in the " - "record/replay buffer."), &info_record_cmdlist); + + add_cmd ("goto", class_obscure, cmd_record_goto, _("\ +Restore the program to its state at instruction number N.\n\ +Argument is instruction number, as shown by 'info record'."), + &record_cmdlist); + + add_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); + }