| /* Process record and replay target for GDB, the GNU debugger. | |
| Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc. | |
| This file is part of GDB. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ | |
| #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 <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 target_signal sigval; | |
| ULONGEST insn_num; | |
| }; | |
| enum record_type | |
| { | |
| record_end = 0, | |
| record_reg, | |
| record_mem | |
| }; | |
| /* This is the data structure that makes up the execution log. | |
| The execution log consists of a single linked list of entries | |
| of type "struct record_entry". It is doubly linked so that it | |
| can be traversed in either direction. | |
| The start of the list is anchored by a struct called | |
| "record_first". The pointer "record_list" either points to the | |
| last entry that was added to the list (in record mode), or to the | |
| next entry in the list that will be executed (in replay mode). | |
| Each list element (struct record_entry), in addition to next and | |
| prev pointers, consists of a union of three entry types: mem, reg, | |
| and end. A field called "type" determines which entry type is | |
| represented by a given list element. | |
| Each instruction that is added to the execution log is represented | |
| by a variable number of list elements ('entries'). The instruction | |
| will have one "reg" entry for each register that is changed by | |
| executing the instruction (including the PC in every case). It | |
| will also have one "mem" entry for each memory change. Finally, | |
| each instruction will have an "end" entry that separates it from | |
| the changes associated with the next instruction. */ | |
| struct record_entry | |
| { | |
| struct record_entry *prev; | |
| struct record_entry *next; | |
| enum record_type type; | |
| union | |
| { | |
| /* reg */ | |
| struct record_reg_entry reg; | |
| /* mem */ | |
| struct record_mem_entry mem; | |
| /* end */ | |
| struct record_end_entry end; | |
| } u; | |
| }; | |
| /* This is the debug switch for process record. */ | |
| int record_debug = 0; | |
| /* If true, query if PREC cannot record memory | |
| change of next instruction. */ | |
| int record_memory_query = 0; | |
| struct record_core_buf_entry | |
| { | |
| struct record_core_buf_entry *prev; | |
| struct target_section *p; | |
| bfd_byte *buf; | |
| }; | |
| /* Record buf with core target. */ | |
| static gdb_byte *record_core_regbuf = NULL; | |
| static struct target_section *record_core_start; | |
| static struct target_section *record_core_end; | |
| static struct record_core_buf_entry *record_core_buf_list = NULL; | |
| /* The following variables are used for managing the linked list that | |
| represents the execution log. | |
| record_first is the anchor that holds down the beginning of the list. | |
| record_list serves two functions: | |
| 1) In record mode, it anchors the end of the list. | |
| 2) In replay mode, it traverses the list and points to | |
| the next instruction that must be emulated. | |
| record_arch_list_head and record_arch_list_tail are used to manage | |
| a separate list, which is used to build up the change elements of | |
| the currently executing instruction during record mode. When this | |
| instruction has been completely annotated in the "arch list", it | |
| will be appended to the main execution log. */ | |
| static struct record_entry record_first; | |
| static struct record_entry *record_list = &record_first; | |
| static struct record_entry *record_arch_list_head = NULL; | |
| static struct record_entry *record_arch_list_tail = NULL; | |
| /* 1 ask user. 0 auto delete the last struct record_entry. */ | |
| static int record_stop_at_limit = 1; | |
| /* Maximum allowed number of insns in execution log. */ | |
| static unsigned int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; | |
| /* Actual count of insns presently in execution log. */ | |
| static int record_insn_num = 0; | |
| /* Count of insns logged so far (may be larger | |
| than count of insns presently in execution log). */ | |
| static ULONGEST record_insn_count; | |
| /* The target_ops of process record. */ | |
| static struct target_ops record_ops; | |
| static struct target_ops record_core_ops; | |
| /* The beneath function pointers. */ | |
| static struct target_ops *record_beneath_to_resume_ops; | |
| static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, | |
| enum target_signal); | |
| static struct target_ops *record_beneath_to_wait_ops; | |
| static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, | |
| struct target_waitstatus *, | |
| int); | |
| static struct target_ops *record_beneath_to_store_registers_ops; | |
| static void (*record_beneath_to_store_registers) (struct target_ops *, | |
| struct regcache *, | |
| int regno); | |
| static struct target_ops *record_beneath_to_xfer_partial_ops; | |
| static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops *ops, | |
| enum target_object object, | |
| const char *annex, | |
| gdb_byte *readbuf, | |
| const gdb_byte *writebuf, | |
| ULONGEST offset, | |
| LONGEST len); | |
| static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *, | |
| struct bp_target_info *); | |
| static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, | |
| struct bp_target_info *); | |
| static int (*record_beneath_to_stopped_by_watchpoint) (void); | |
| static int (*record_beneath_to_stopped_data_address) (struct target_ops *, | |
| CORE_ADDR *); | |
| /* 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 (0); | |
| return NULL; | |
| } | |
| } | |
| /* Record the value of a register NUM to record_arch_list. */ | |
| int | |
| record_arch_list_add_reg (struct regcache *regcache, int regnum) | |
| { | |
| struct record_entry *rec; | |
| if (record_debug > 1) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: add register num = %d to " | |
| "record list.\n", | |
| regnum); | |
| rec = record_reg_alloc (regcache, regnum); | |
| regcache_raw_read (regcache, regnum, record_get_loc (rec)); | |
| record_arch_list_add (rec); | |
| return 0; | |
| } | |
| /* Record the value of a region of memory whose address is ADDR and | |
| length is LEN to record_arch_list. */ | |
| int | |
| record_arch_list_add_mem (CORE_ADDR addr, int len) | |
| { | |
| struct record_entry *rec; | |
| if (record_debug > 1) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: add mem addr = %s len = %d to " | |
| "record list.\n", | |
| paddress (target_gdbarch, addr), len); | |
| if (!addr) /* FIXME: Why? Some arch must permit it... */ | |
| return 0; | |
| rec = record_mem_alloc (addr, len); | |
| if (target_read_memory (addr, record_get_loc (rec), len)) | |
| { | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: error reading memory at " | |
| "addr = %s len = %d.\n", | |
| paddress (target_gdbarch, addr), len); | |
| record_mem_release (rec); | |
| return -1; | |
| } | |
| record_arch_list_add (rec); | |
| return 0; | |
| } | |
| /* Add a record_end type struct record_entry to record_arch_list. */ | |
| int | |
| record_arch_list_add_end (void) | |
| { | |
| struct record_entry *rec; | |
| if (record_debug > 1) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: add end to arch list.\n"); | |
| rec = record_end_alloc (); | |
| rec->u.end.sigval = TARGET_SIGNAL_0; | |
| rec->u.end.insn_num = ++record_insn_count; | |
| record_arch_list_add (rec); | |
| return 0; | |
| } | |
| static void | |
| record_check_insn_num (int set_terminal) | |
| { | |
| if (record_insn_max_num) | |
| { | |
| gdb_assert (record_insn_num <= record_insn_max_num); | |
| if (record_insn_num == record_insn_max_num) | |
| { | |
| /* Ask user what to do. */ | |
| if (record_stop_at_limit) | |
| { | |
| int q; | |
| if (set_terminal) | |
| target_terminal_ours (); | |
| q = yquery (_("Do you want to auto delete previous execution " | |
| "log entries when record/replay buffer becomes " | |
| "full (record stop-at-limit)?")); | |
| if (set_terminal) | |
| target_terminal_inferior (); | |
| if (q) | |
| record_stop_at_limit = 0; | |
| else | |
| error (_("Process record: stopped by user.")); | |
| } | |
| } | |
| } | |
| } | |
| static void | |
| record_arch_list_cleanups (void *ignore) | |
| { | |
| record_list_release (record_arch_list_tail); | |
| } | |
| /* Before inferior step (when GDB record the running message, inferior | |
| only can step), GDB will call this function to record the values to | |
| record_list. This function will call gdbarch_process_record to | |
| record the running message of inferior and set them to | |
| record_arch_list, and add it to record_list. */ | |
| static int | |
| record_message (struct regcache *regcache, enum target_signal signal) | |
| { | |
| int ret; | |
| struct gdbarch *gdbarch = get_regcache_arch (regcache); | |
| struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); | |
| record_arch_list_head = NULL; | |
| record_arch_list_tail = NULL; | |
| /* Check record_insn_num. */ | |
| record_check_insn_num (1); | |
| /* If gdb sends a signal value to target_resume, | |
| save it in the 'end' field of the previous instruction. | |
| Maybe process record should record what really happened, | |
| rather than what gdb pretends has happened. | |
| So if Linux delivered the signal to the child process during | |
| the record mode, we will record it and deliver it again in | |
| the replay mode. | |
| If user says "ignore this signal" during the record mode, then | |
| it will be ignored again during the replay mode (no matter if | |
| the user says something different, like "deliver this signal" | |
| during the replay mode). | |
| User should understand that nothing he does during the replay | |
| mode will change the behavior of the child. If he tries, | |
| then that is a user error. | |
| But we should still deliver the signal to gdb during the replay, | |
| if we delivered it during the recording. Therefore we should | |
| record the signal during record_wait, not record_resume. */ | |
| if (record_list != &record_first) /* FIXME better way to check */ | |
| { | |
| gdb_assert (record_list->type == record_end); | |
| record_list->u.end.sigval = signal; | |
| } | |
| if (signal == TARGET_SIGNAL_0 | |
| || !gdbarch_process_record_signal_p (gdbarch)) | |
| ret = gdbarch_process_record (gdbarch, | |
| regcache, | |
| regcache_read_pc (regcache)); | |
| else | |
| ret = gdbarch_process_record_signal (gdbarch, | |
| regcache, | |
| signal); | |
| if (ret > 0) | |
| error (_("Process record: inferior program stopped.")); | |
| if (ret < 0) | |
| error (_("Process record: failed to record execution log.")); | |
| discard_cleanups (old_cleanups); | |
| record_list->next = record_arch_list_head; | |
| record_arch_list_head->prev = record_list; | |
| record_list = record_arch_list_tail; | |
| if (record_insn_num == record_insn_max_num && record_insn_max_num) | |
| record_list_release_first (); | |
| else | |
| record_insn_num++; | |
| return 1; | |
| } | |
| struct record_message_args { | |
| struct regcache *regcache; | |
| enum target_signal signal; | |
| }; | |
| static int | |
| record_message_wrapper (void *args) | |
| { | |
| struct record_message_args *record_args = args; | |
| return record_message (record_args->regcache, record_args->signal); | |
| } | |
| static int | |
| record_message_wrapper_safe (struct regcache *regcache, | |
| enum target_signal signal) | |
| { | |
| struct record_message_args args; | |
| args.regcache = regcache; | |
| args.signal = signal; | |
| return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL); | |
| } | |
| /* Set to 1 if record_store_registers and record_xfer_partial | |
| doesn't need record. */ | |
| static int record_gdb_operation_disable = 0; | |
| struct cleanup * | |
| record_gdb_operation_disable_set (void) | |
| { | |
| struct cleanup *old_cleanups = NULL; | |
| old_cleanups = | |
| make_cleanup_restore_integer (&record_gdb_operation_disable); | |
| record_gdb_operation_disable = 1; | |
| return old_cleanups; | |
| } | |
| /* Flag set to TRUE for target_stopped_by_watchpoint. */ | |
| static int record_hw_watchpoint = 0; | |
| /* Execute one instruction from the record log. Each instruction in | |
| the log will be represented by an arbitrary sequence of register | |
| entries and memory entries, followed by an 'end' entry. */ | |
| static inline void | |
| record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch, | |
| struct record_entry *entry) | |
| { | |
| switch (entry->type) | |
| { | |
| case record_reg: /* reg */ | |
| { | |
| gdb_byte reg[MAX_REGISTER_SIZE]; | |
| if (record_debug > 1) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: record_reg %s to " | |
| "inferior num = %d.\n", | |
| host_address_to_string (entry), | |
| entry->u.reg.num); | |
| regcache_cooked_read (regcache, entry->u.reg.num, reg); | |
| regcache_cooked_write (regcache, entry->u.reg.num, | |
| record_get_loc (entry)); | |
| memcpy (record_get_loc (entry), reg, entry->u.reg.len); | |
| } | |
| break; | |
| case record_mem: /* mem */ | |
| { | |
| /* Nothing to do if the entry is flagged not_accessible. */ | |
| if (!entry->u.mem.mem_entry_not_accessible) | |
| { | |
| gdb_byte *mem = alloca (entry->u.mem.len); | |
| if (record_debug > 1) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: record_mem %s to " | |
| "inferior addr = %s len = %d.\n", | |
| host_address_to_string (entry), | |
| paddress (gdbarch, entry->u.mem.addr), | |
| entry->u.mem.len); | |
| if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) | |
| { | |
| entry->u.mem.mem_entry_not_accessible = 1; | |
| if (record_debug) | |
| warning ("Process record: error reading memory at " | |
| "addr = %s len = %d.", | |
| paddress (gdbarch, entry->u.mem.addr), | |
| entry->u.mem.len); | |
| } | |
| else | |
| { | |
| if (target_write_memory (entry->u.mem.addr, | |
| record_get_loc (entry), | |
| entry->u.mem.len)) | |
| { | |
| entry->u.mem.mem_entry_not_accessible = 1; | |
| if (record_debug) | |
| warning ("Process record: error writing memory at " | |
| "addr = %s len = %d.", | |
| paddress (gdbarch, entry->u.mem.addr), | |
| entry->u.mem.len); | |
| } | |
| else | |
| { | |
| memcpy (record_get_loc (entry), mem, entry->u.mem.len); | |
| /* We've changed memory --- check if a hardware | |
| watchpoint should trap. Note that this | |
| presently assumes the target beneath supports | |
| continuable watchpoints. On non-continuable | |
| watchpoints target, we'll want to check this | |
| _before_ actually doing the memory change, and | |
| not doing the change at all if the watchpoint | |
| traps. */ | |
| if (hardware_watchpoint_inserted_in_range | |
| (get_regcache_aspace (regcache), | |
| entry->u.mem.addr, entry->u.mem.len)) | |
| record_hw_watchpoint = 1; | |
| } | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| static struct target_ops *tmp_to_resume_ops; | |
| static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, | |
| enum target_signal); | |
| static struct target_ops *tmp_to_wait_ops; | |
| static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, | |
| struct target_waitstatus *, | |
| int); | |
| static struct target_ops *tmp_to_store_registers_ops; | |
| static void (*tmp_to_store_registers) (struct target_ops *, | |
| struct regcache *, | |
| int regno); | |
| static struct target_ops *tmp_to_xfer_partial_ops; | |
| static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, | |
| enum target_object object, | |
| const char *annex, | |
| gdb_byte *readbuf, | |
| const gdb_byte *writebuf, | |
| ULONGEST offset, | |
| LONGEST len); | |
| static int (*tmp_to_insert_breakpoint) (struct gdbarch *, | |
| struct bp_target_info *); | |
| static int (*tmp_to_remove_breakpoint) (struct gdbarch *, | |
| struct bp_target_info *); | |
| static int (*tmp_to_stopped_by_watchpoint) (void); | |
| static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *); | |
| static void record_restore (void); | |
| /* Open the process record target. */ | |
| static void | |
| record_core_open_1 (char *name, int from_tty) | |
| { | |
| struct regcache *regcache = get_current_regcache (); | |
| int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); | |
| int i; | |
| /* Get record_core_regbuf. */ | |
| target_fetch_registers (regcache, -1); | |
| record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); | |
| for (i = 0; i < regnum; i ++) | |
| regcache_raw_collect (regcache, i, | |
| record_core_regbuf + MAX_REGISTER_SIZE * i); | |
| /* Get record_core_start and record_core_end. */ | |
| if (build_section_table (core_bfd, &record_core_start, &record_core_end)) | |
| { | |
| xfree (record_core_regbuf); | |
| record_core_regbuf = NULL; | |
| error (_("\"%s\": Can't find sections: %s"), | |
| bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); | |
| } | |
| push_target (&record_core_ops); | |
| record_restore (); | |
| } | |
| /* "to_open" target method for 'live' processes. */ | |
| static void | |
| record_open_1 (char *name, int from_tty) | |
| { | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); | |
| /* check exec */ | |
| if (!target_has_execution) | |
| error (_("Process record: the program is not being run.")); | |
| if (non_stop) | |
| error (_("Process record target can't debug inferior in non-stop mode " | |
| "(non-stop).")); | |
| if (target_async_permitted) | |
| error (_("Process record target can't debug inferior in asynchronous " | |
| "mode (target-async).")); | |
| if (!gdbarch_process_record_p (target_gdbarch)) | |
| error (_("Process record: the current architecture doesn't support " | |
| "record function.")); | |
| if (!tmp_to_resume) | |
| error (_("Could not find 'to_resume' method on the target stack.")); | |
| if (!tmp_to_wait) | |
| error (_("Could not find 'to_wait' method on the target stack.")); | |
| if (!tmp_to_store_registers) | |
| error (_("Could not find 'to_store_registers' method on the target stack.")); | |
| if (!tmp_to_insert_breakpoint) | |
| error (_("Could not find 'to_insert_breakpoint' method on the target stack.")); | |
| if (!tmp_to_remove_breakpoint) | |
| error (_("Could not find 'to_remove_breakpoint' method on the target stack.")); | |
| if (!tmp_to_stopped_by_watchpoint) | |
| error (_("Could not find 'to_stopped_by_watchpoint' method on the target stack.")); | |
| if (!tmp_to_stopped_data_address) | |
| error (_("Could not find 'to_stopped_data_address' method on the target stack.")); | |
| push_target (&record_ops); | |
| } | |
| /* "to_open" target method. Open the process record target. */ | |
| static void | |
| record_open (char *name, int from_tty) | |
| { | |
| struct target_ops *t; | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); | |
| /* Check if record target is already running. */ | |
| if (current_target.to_stratum == record_stratum) | |
| error (_("Process record target already running. Use \"record stop\" to " | |
| "stop record target first.")); | |
| /* Reset the tmp beneath pointers. */ | |
| tmp_to_resume_ops = NULL; | |
| tmp_to_resume = NULL; | |
| tmp_to_wait_ops = NULL; | |
| tmp_to_wait = NULL; | |
| tmp_to_store_registers_ops = NULL; | |
| tmp_to_store_registers = NULL; | |
| tmp_to_xfer_partial_ops = NULL; | |
| tmp_to_xfer_partial = NULL; | |
| tmp_to_insert_breakpoint = NULL; | |
| tmp_to_remove_breakpoint = NULL; | |
| tmp_to_stopped_by_watchpoint = NULL; | |
| tmp_to_stopped_data_address = NULL; | |
| /* Set the beneath function pointers. */ | |
| for (t = current_target.beneath; t != NULL; t = t->beneath) | |
| { | |
| if (!tmp_to_resume) | |
| { | |
| tmp_to_resume = t->to_resume; | |
| tmp_to_resume_ops = t; | |
| } | |
| if (!tmp_to_wait) | |
| { | |
| tmp_to_wait = t->to_wait; | |
| tmp_to_wait_ops = t; | |
| } | |
| if (!tmp_to_store_registers) | |
| { | |
| tmp_to_store_registers = t->to_store_registers; | |
| tmp_to_store_registers_ops = t; | |
| } | |
| if (!tmp_to_xfer_partial) | |
| { | |
| tmp_to_xfer_partial = t->to_xfer_partial; | |
| tmp_to_xfer_partial_ops = t; | |
| } | |
| if (!tmp_to_insert_breakpoint) | |
| tmp_to_insert_breakpoint = t->to_insert_breakpoint; | |
| if (!tmp_to_remove_breakpoint) | |
| tmp_to_remove_breakpoint = t->to_remove_breakpoint; | |
| if (!tmp_to_stopped_by_watchpoint) | |
| tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint; | |
| if (!tmp_to_stopped_data_address) | |
| tmp_to_stopped_data_address = t->to_stopped_data_address; | |
| } | |
| if (!tmp_to_xfer_partial) | |
| error (_("Could not find 'to_xfer_partial' method on the target stack.")); | |
| /* Reset */ | |
| record_insn_num = 0; | |
| record_insn_count = 0; | |
| record_list = &record_first; | |
| record_list->next = NULL; | |
| /* Set the tmp beneath pointers to beneath pointers. */ | |
| record_beneath_to_resume_ops = tmp_to_resume_ops; | |
| record_beneath_to_resume = tmp_to_resume; | |
| record_beneath_to_wait_ops = tmp_to_wait_ops; | |
| record_beneath_to_wait = tmp_to_wait; | |
| record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; | |
| record_beneath_to_store_registers = tmp_to_store_registers; | |
| record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; | |
| record_beneath_to_xfer_partial = tmp_to_xfer_partial; | |
| record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; | |
| record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; | |
| record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint; | |
| record_beneath_to_stopped_data_address = tmp_to_stopped_data_address; | |
| if (current_target.to_stratum == core_stratum) | |
| record_core_open_1 (name, from_tty); | |
| else | |
| record_open_1 (name, from_tty); | |
| } | |
| /* "to_close" target method. Close the process record target. */ | |
| static void | |
| record_close (int quitting) | |
| { | |
| struct record_core_buf_entry *entry; | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); | |
| record_list_release (record_list); | |
| /* Release record_core_regbuf. */ | |
| if (record_core_regbuf) | |
| { | |
| xfree (record_core_regbuf); | |
| record_core_regbuf = NULL; | |
| } | |
| /* Release record_core_buf_list. */ | |
| if (record_core_buf_list) | |
| { | |
| for (entry = record_core_buf_list->prev; entry; entry = entry->prev) | |
| { | |
| xfree (record_core_buf_list); | |
| record_core_buf_list = entry; | |
| } | |
| record_core_buf_list = NULL; | |
| } | |
| } | |
| static int record_resume_step = 0; | |
| /* "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; | |
| if (!RECORD_IS_REPLAY) | |
| { | |
| record_message (get_current_regcache (), signal); | |
| record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, | |
| signal); | |
| } | |
| } | |
| 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 (struct target_ops *ops, | |
| ptid_t ptid, struct target_waitstatus *status, | |
| int options) | |
| { | |
| struct cleanup *set_cleanups = record_gdb_operation_disable_set (); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: record_wait " | |
| "record_resume_step = %d\n", | |
| record_resume_step); | |
| record_get_sig = 0; | |
| signal (SIGINT, record_sig_handler); | |
| if (!RECORD_IS_REPLAY && ops != &record_core_ops) | |
| { | |
| if (record_resume_step) | |
| { | |
| /* This is a single step. */ | |
| return record_beneath_to_wait (record_beneath_to_wait_ops, | |
| ptid, status, options); | |
| } | |
| else | |
| { | |
| /* This is not a single step. */ | |
| ptid_t ret; | |
| CORE_ADDR tmp_pc; | |
| while (1) | |
| { | |
| ret = record_beneath_to_wait (record_beneath_to_wait_ops, | |
| ptid, status, options); | |
| if (record_resume_step) | |
| return ret; | |
| /* Is this a SIGTRAP? */ | |
| if (status->kind == TARGET_WAITKIND_STOPPED | |
| && status->value.sig == TARGET_SIGNAL_TRAP) | |
| { | |
| struct regcache *regcache; | |
| struct address_space *aspace; | |
| /* Yes -- this is likely our single-step finishing, | |
| but check if there's any reason the core would be | |
| interested in the event. */ | |
| registers_changed (); | |
| regcache = get_current_regcache (); | |
| tmp_pc = regcache_read_pc (regcache); | |
| aspace = get_regcache_aspace (regcache); | |
| if (target_stopped_by_watchpoint ()) | |
| { | |
| /* Always interested in watchpoints. */ | |
| } | |
| else if (breakpoint_inserted_here_p (aspace, tmp_pc)) | |
| { | |
| /* There is a breakpoint here. Let the core | |
| handle it. */ | |
| if (software_breakpoint_inserted_here_p (aspace, tmp_pc)) | |
| { | |
| struct gdbarch *gdbarch = get_regcache_arch (regcache); | |
| CORE_ADDR decr_pc_after_break | |
| = gdbarch_decr_pc_after_break (gdbarch); | |
| if (decr_pc_after_break) | |
| regcache_write_pc (regcache, | |
| tmp_pc + decr_pc_after_break); | |
| } | |
| } | |
| else | |
| { | |
| /* This must be a single-step trap. Record the | |
| insn and issue another step. */ | |
| if (!record_message_wrapper_safe (regcache, | |
| TARGET_SIGNAL_0)) | |
| { | |
| status->kind = TARGET_WAITKIND_STOPPED; | |
| status->value.sig = TARGET_SIGNAL_0; | |
| break; | |
| } | |
| record_beneath_to_resume (record_beneath_to_resume_ops, | |
| ptid, 1, | |
| TARGET_SIGNAL_0); | |
| continue; | |
| } | |
| } | |
| /* The inferior is broken by a breakpoint or a signal. */ | |
| break; | |
| } | |
| return ret; | |
| } | |
| } | |
| else | |
| { | |
| struct regcache *regcache = get_current_regcache (); | |
| struct gdbarch *gdbarch = get_regcache_arch (regcache); | |
| struct address_space *aspace = get_regcache_aspace (regcache); | |
| int continue_flag = 1; | |
| int first_record_end = 1; | |
| struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); | |
| CORE_ADDR tmp_pc; | |
| record_hw_watchpoint = 0; | |
| status->kind = TARGET_WAITKIND_STOPPED; | |
| /* Check breakpoint when forward execute. */ | |
| if (execution_direction == EXEC_FORWARD) | |
| { | |
| tmp_pc = regcache_read_pc (regcache); | |
| if (breakpoint_inserted_here_p (aspace, tmp_pc)) | |
| { | |
| int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: break at %s.\n", | |
| paddress (gdbarch, tmp_pc)); | |
| if (decr_pc_after_break | |
| && !record_resume_step | |
| && software_breakpoint_inserted_here_p (aspace, tmp_pc)) | |
| regcache_write_pc (regcache, | |
| tmp_pc + decr_pc_after_break); | |
| goto replay_out; | |
| } | |
| } | |
| /* If GDB is in terminal_inferior mode, it will not get the signal. | |
| And in GDB replay mode, GDB doesn't need to be in terminal_inferior | |
| mode, because inferior will not executed. | |
| Then set it to terminal_ours to make GDB get the signal. */ | |
| target_terminal_ours (); | |
| /* In EXEC_FORWARD mode, record_list points to the tail of prev | |
| instruction. */ | |
| if (execution_direction == EXEC_FORWARD && record_list->next) | |
| record_list = record_list->next; | |
| /* Loop over the record_list, looking for the next place to | |
| stop. */ | |
| do | |
| { | |
| /* Check for beginning and end of log. */ | |
| if (execution_direction == EXEC_REVERSE | |
| && record_list == &record_first) | |
| { | |
| /* Hit beginning of record log in reverse. */ | |
| status->kind = TARGET_WAITKIND_NO_HISTORY; | |
| break; | |
| } | |
| if (execution_direction != EXEC_REVERSE && !record_list->next) | |
| { | |
| /* Hit end of record log going forward. */ | |
| status->kind = TARGET_WAITKIND_NO_HISTORY; | |
| break; | |
| } | |
| record_exec_insn (regcache, gdbarch, record_list); | |
| if (record_list->type == record_end) | |
| { | |
| if (record_debug > 1) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: record_end %s to " | |
| "inferior.\n", | |
| host_address_to_string (record_list)); | |
| if (first_record_end && execution_direction == EXEC_REVERSE) | |
| { | |
| /* When reverse excute, the first record_end is the part of | |
| current instruction. */ | |
| first_record_end = 0; | |
| } | |
| else | |
| { | |
| /* In EXEC_REVERSE mode, this is the record_end of prev | |
| instruction. | |
| In EXEC_FORWARD mode, this is the record_end of current | |
| instruction. */ | |
| /* step */ | |
| if (record_resume_step) | |
| { | |
| if (record_debug > 1) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: step.\n"); | |
| continue_flag = 0; | |
| } | |
| /* check breakpoint */ | |
| tmp_pc = regcache_read_pc (regcache); | |
| if (breakpoint_inserted_here_p (aspace, tmp_pc)) | |
| { | |
| int decr_pc_after_break | |
| = gdbarch_decr_pc_after_break (gdbarch); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: break " | |
| "at %s.\n", | |
| paddress (gdbarch, tmp_pc)); | |
| if (decr_pc_after_break | |
| && execution_direction == EXEC_FORWARD | |
| && !record_resume_step | |
| && software_breakpoint_inserted_here_p (aspace, | |
| tmp_pc)) | |
| regcache_write_pc (regcache, | |
| tmp_pc + decr_pc_after_break); | |
| continue_flag = 0; | |
| } | |
| if (record_hw_watchpoint) | |
| { | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "\ | |
| Process record: hit hw watchpoint.\n"); | |
| continue_flag = 0; | |
| } | |
| /* Check target signal */ | |
| if (record_list->u.end.sigval != TARGET_SIGNAL_0) | |
| /* FIXME: better way to check */ | |
| continue_flag = 0; | |
| } | |
| } | |
| if (continue_flag) | |
| { | |
| if (execution_direction == EXEC_REVERSE) | |
| { | |
| if (record_list->prev) | |
| record_list = record_list->prev; | |
| } | |
| else | |
| { | |
| if (record_list->next) | |
| record_list = record_list->next; | |
| } | |
| } | |
| } | |
| while (continue_flag); | |
| replay_out: | |
| if (record_get_sig) | |
| status->value.sig = TARGET_SIGNAL_INT; | |
| else if (record_list->u.end.sigval != TARGET_SIGNAL_0) | |
| /* FIXME: better way to check */ | |
| status->value.sig = record_list->u.end.sigval; | |
| else | |
| status->value.sig = TARGET_SIGNAL_TRAP; | |
| discard_cleanups (old_cleanups); | |
| } | |
| signal (SIGINT, handle_sigint); | |
| do_cleanups (set_cleanups); | |
| return inferior_ptid; | |
| } | |
| static int | |
| record_stopped_by_watchpoint (void) | |
| { | |
| if (RECORD_IS_REPLAY) | |
| return record_hw_watchpoint; | |
| else | |
| return record_beneath_to_stopped_by_watchpoint (); | |
| } | |
| static int | |
| record_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p) | |
| { | |
| if (RECORD_IS_REPLAY) | |
| return 0; | |
| else | |
| return record_beneath_to_stopped_data_address (ops, addr_p); | |
| } | |
| /* "to_disconnect" method for process record target. */ | |
| static void | |
| record_disconnect (struct target_ops *target, char *args, int from_tty) | |
| { | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); | |
| unpush_target (&record_ops); | |
| target_disconnect (args, from_tty); | |
| } | |
| /* "to_detach" method for process record target. */ | |
| static void | |
| record_detach (struct target_ops *ops, char *args, int from_tty) | |
| { | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); | |
| unpush_target (&record_ops); | |
| target_detach (args, from_tty); | |
| } | |
| /* "to_mourn_inferior" method for process record target. */ | |
| static void | |
| record_mourn_inferior (struct target_ops *ops) | |
| { | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Process record: " | |
| "record_mourn_inferior\n"); | |
| unpush_target (&record_ops); | |
| target_mourn_inferior (); | |
| } | |
| /* Close process record target before killing the inferior process. */ | |
| static void | |
| record_kill (struct target_ops *ops) | |
| { | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); | |
| unpush_target (&record_ops); | |
| target_kill (); | |
| } | |
| /* Record registers change (by user or by GDB) to list as an instruction. */ | |
| static void | |
| record_registers_change (struct regcache *regcache, int regnum) | |
| { | |
| /* Check record_insn_num. */ | |
| record_check_insn_num (0); | |
| record_arch_list_head = NULL; | |
| record_arch_list_tail = NULL; | |
| if (regnum < 0) | |
| { | |
| int i; | |
| for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) | |
| { | |
| if (record_arch_list_add_reg (regcache, i)) | |
| { | |
| record_list_release (record_arch_list_tail); | |
| error (_("Process record: failed to record execution log.")); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| if (record_arch_list_add_reg (regcache, regnum)) | |
| { | |
| record_list_release (record_arch_list_tail); | |
| error (_("Process record: failed to record execution log.")); | |
| } | |
| } | |
| if (record_arch_list_add_end ()) | |
| { | |
| record_list_release (record_arch_list_tail); | |
| error (_("Process record: failed to record execution log.")); | |
| } | |
| record_list->next = record_arch_list_head; | |
| record_arch_list_head->prev = record_list; | |
| record_list = record_arch_list_tail; | |
| if (record_insn_num == record_insn_max_num && record_insn_max_num) | |
| record_list_release_first (); | |
| else | |
| record_insn_num++; | |
| } | |
| /* "to_store_registers" method for process record target. */ | |
| static void | |
| record_store_registers (struct target_ops *ops, struct regcache *regcache, | |
| int regno) | |
| { | |
| if (!record_gdb_operation_disable) | |
| { | |
| if (RECORD_IS_REPLAY) | |
| { | |
| int n; | |
| /* Let user choose if he wants to write register or not. */ | |
| if (regno < 0) | |
| n = | |
| query (_("Because GDB is in replay mode, changing the " | |
| "value of a register will make the execution " | |
| "log unusable from this point onward. " | |
| "Change all registers?")); | |
| else | |
| n = | |
| query (_("Because GDB is in replay mode, changing the value " | |
| "of a register will make the execution log unusable " | |
| "from this point onward. Change register %s?"), | |
| gdbarch_register_name (get_regcache_arch (regcache), | |
| regno)); | |
| if (!n) | |
| { | |
| /* Invalidate the value of regcache that was set in function | |
| "regcache_raw_write". */ | |
| if (regno < 0) | |
| { | |
| int i; | |
| for (i = 0; | |
| i < gdbarch_num_regs (get_regcache_arch (regcache)); | |
| i++) | |
| regcache_invalidate (regcache, i); | |
| } | |
| else | |
| regcache_invalidate (regcache, regno); | |
| error (_("Process record canceled the operation.")); | |
| } | |
| /* Destroy the record from here forward. */ | |
| record_list_release_following (record_list); | |
| } | |
| record_registers_change (regcache, regno); | |
| } | |
| record_beneath_to_store_registers (record_beneath_to_store_registers_ops, | |
| regcache, regno); | |
| } | |
| /* "to_xfer_partial" method. Behavior is conditional on RECORD_IS_REPLAY. | |
| In replay mode, we cannot write memory unles we are willing to | |
| invalidate the record/replay log from this point forward. */ | |
| static LONGEST | |
| record_xfer_partial (struct target_ops *ops, enum target_object object, | |
| const char *annex, gdb_byte *readbuf, | |
| const gdb_byte *writebuf, ULONGEST offset, LONGEST len) | |
| { | |
| if (!record_gdb_operation_disable | |
| && (object == TARGET_OBJECT_MEMORY | |
| || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) | |
| { | |
| if (RECORD_IS_REPLAY) | |
| { | |
| /* Let user choose if he wants to write memory or not. */ | |
| if (!query (_("Because GDB is in replay mode, writing to memory " | |
| "will make the execution log unusable from this " | |
| "point onward. Write memory at address %s?"), | |
| paddress (target_gdbarch, offset))) | |
| error (_("Process record canceled the operation.")); | |
| /* Destroy the record from here forward. */ | |
| record_list_release_following (record_list); | |
| } | |
| /* Check record_insn_num */ | |
| record_check_insn_num (0); | |
| /* Record registers change to list as an instruction. */ | |
| record_arch_list_head = NULL; | |
| record_arch_list_tail = NULL; | |
| if (record_arch_list_add_mem (offset, len)) | |
| { | |
| record_list_release (record_arch_list_tail); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: failed to record " | |
| "execution log."); | |
| return -1; | |
| } | |
| if (record_arch_list_add_end ()) | |
| { | |
| record_list_release (record_arch_list_tail); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, | |
| "Process record: failed to record " | |
| "execution log."); | |
| return -1; | |
| } | |
| record_list->next = record_arch_list_head; | |
| record_arch_list_head->prev = record_list; | |
| record_list = record_arch_list_tail; | |
| if (record_insn_num == record_insn_max_num && record_insn_max_num) | |
| record_list_release_first (); | |
| else | |
| record_insn_num++; | |
| } | |
| return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, | |
| object, annex, readbuf, writebuf, | |
| offset, len); | |
| } | |
| /* Behavior is conditional on RECORD_IS_REPLAY. | |
| We will not actually insert or remove breakpoints when replaying, | |
| nor when recording. */ | |
| static int | |
| record_insert_breakpoint (struct gdbarch *gdbarch, | |
| struct bp_target_info *bp_tgt) | |
| { | |
| if (!RECORD_IS_REPLAY) | |
| { | |
| struct cleanup *old_cleanups = record_gdb_operation_disable_set (); | |
| int ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt); | |
| do_cleanups (old_cleanups); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| /* "to_remove_breakpoint" method for process record target. */ | |
| static int | |
| record_remove_breakpoint (struct gdbarch *gdbarch, | |
| struct bp_target_info *bp_tgt) | |
| { | |
| if (!RECORD_IS_REPLAY) | |
| { | |
| struct cleanup *old_cleanups = record_gdb_operation_disable_set (); | |
| int ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt); | |
| do_cleanups (old_cleanups); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| /* "to_can_execute_reverse" method for process record target. */ | |
| static int | |
| record_can_execute_reverse (void) | |
| { | |
| return 1; | |
| } | |
| /* "to_get_bookmark" method for process record and prec over core. */ | |
| 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 | |
| init_record_ops (void) | |
| { | |
| record_ops.to_shortname = "record"; | |
| record_ops.to_longname = "Process record and replay target"; | |
| record_ops.to_doc = | |
| "Log program while executing and replay execution from log."; | |
| record_ops.to_open = record_open; | |
| record_ops.to_close = record_close; | |
| record_ops.to_resume = record_resume; | |
| record_ops.to_wait = record_wait; | |
| record_ops.to_disconnect = record_disconnect; | |
| record_ops.to_detach = record_detach; | |
| record_ops.to_mourn_inferior = record_mourn_inferior; | |
| record_ops.to_kill = record_kill; | |
| record_ops.to_create_inferior = find_default_create_inferior; | |
| record_ops.to_store_registers = record_store_registers; | |
| record_ops.to_xfer_partial = record_xfer_partial; | |
| record_ops.to_insert_breakpoint = record_insert_breakpoint; | |
| record_ops.to_remove_breakpoint = record_remove_breakpoint; | |
| record_ops.to_stopped_by_watchpoint = record_stopped_by_watchpoint; | |
| record_ops.to_stopped_data_address = record_stopped_data_address; | |
| record_ops.to_can_execute_reverse = record_can_execute_reverse; | |
| record_ops.to_stratum = record_stratum; | |
| /* Add bookmark target methods. */ | |
| record_ops.to_get_bookmark = record_get_bookmark; | |
| record_ops.to_goto_bookmark = record_goto_bookmark; | |
| record_ops.to_magic = OPS_MAGIC; | |
| } | |
| /* "to_resume" method for prec over corefile. */ | |
| static void | |
| record_core_resume (struct target_ops *ops, ptid_t ptid, int step, | |
| enum target_signal signal) | |
| { | |
| record_resume_step = step; | |
| } | |
| /* "to_kill" method for prec over corefile. */ | |
| static void | |
| record_core_kill (struct target_ops *ops) | |
| { | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); | |
| unpush_target (&record_core_ops); | |
| } | |
| /* "to_fetch_registers" method for prec over corefile. */ | |
| static void | |
| record_core_fetch_registers (struct target_ops *ops, | |
| struct regcache *regcache, | |
| int regno) | |
| { | |
| 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. */ | |
| int | |
| record_core_has_execution (struct target_ops *ops) | |
| { | |
| 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_magic = OPS_MAGIC; | |
| } | |
| /* Implement "show record debug" command. */ | |
| static void | |
| show_record_debug (struct ui_file *file, int from_tty, | |
| struct cmd_list_element *c, const char *value) | |
| { | |
| fprintf_filtered (file, _("Debugging of process record target is %s.\n"), | |
| value); | |
| } | |
| /* Alias for "target record". */ | |
| static void | |
| cmd_record_start (char *args, int from_tty) | |
| { | |
| execute_command ("target record", from_tty); | |
| } | |
| /* Truncate the record log from the present point | |
| of replay until the end. */ | |
| static void | |
| cmd_record_delete (char *args, int from_tty) | |
| { | |
| if (current_target.to_stratum == record_stratum) | |
| { | |
| if (RECORD_IS_REPLAY) | |
| { | |
| if (!from_tty || query (_("Delete the log from this point forward " | |
| "and begin to record the running message " | |
| "at current PC?"))) | |
| record_list_release_following (record_list); | |
| } | |
| else | |
| printf_unfiltered (_("Already at end of record list.\n")); | |
| } | |
| else | |
| printf_unfiltered (_("Process record is not started.\n")); | |
| } | |
| /* Implement the "stoprecord" or "record stop" command. */ | |
| static void | |
| cmd_record_stop (char *args, int from_tty) | |
| { | |
| if (current_target.to_stratum == record_stratum) | |
| { | |
| unpush_target (&record_ops); | |
| printf_unfiltered (_("Process record is stopped and all execution " | |
| "logs are deleted.\n")); | |
| } | |
| else | |
| printf_unfiltered (_("Process record is not started.\n")); | |
| } | |
| /* Set upper limit of record log size. */ | |
| static void | |
| set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) | |
| { | |
| if (record_insn_num > record_insn_max_num && record_insn_max_num) | |
| { | |
| /* Count down record_insn_num while releasing records from list. */ | |
| while (record_insn_num > record_insn_max_num) | |
| { | |
| record_list_release_first (); | |
| record_insn_num--; | |
| } | |
| } | |
| } | |
| static struct cmd_list_element *record_cmdlist, *set_record_cmdlist, | |
| *show_record_cmdlist, *info_record_cmdlist; | |
| static void | |
| set_record_command (char *args, int from_tty) | |
| { | |
| printf_unfiltered (_("\ | |
| \"set record\" must be followed by an apporpriate subcommand.\n")); | |
| help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout); | |
| } | |
| static void | |
| show_record_command (char *args, int from_tty) | |
| { | |
| cmd_show_list (show_record_cmdlist, from_tty, ""); | |
| } | |
| /* Display some statistics about the execution log. */ | |
| static void | |
| info_record_command (char *args, int from_tty) | |
| { | |
| struct record_entry *p; | |
| if (current_target.to_stratum == record_stratum) | |
| { | |
| if (RECORD_IS_REPLAY) | |
| printf_filtered (_("Replay mode:\n")); | |
| else | |
| printf_filtered (_("Record mode:\n")); | |
| /* Find entry for first actual instruction in the log. */ | |
| for (p = record_first.next; | |
| p != NULL && p->type != record_end; | |
| p = p->next) | |
| ; | |
| /* Do we have a log at all? */ | |
| if (p != NULL && p->type == record_end) | |
| { | |
| /* Display instruction number for first instruction in the log. */ | |
| printf_filtered (_("Lowest recorded instruction number is %s.\n"), | |
| pulongest (p->u.end.insn_num)); | |
| /* If in replay mode, display where we are in the log. */ | |
| if (RECORD_IS_REPLAY) | |
| printf_filtered (_("Current instruction number is %s.\n"), | |
| pulongest (record_list->u.end.insn_num)); | |
| /* Display instruction number for last instruction in the log. */ | |
| printf_filtered (_("Highest recorded instruction number is %s.\n"), | |
| pulongest (record_insn_count)); | |
| /* Display log count. */ | |
| printf_filtered (_("Log contains %d instructions.\n"), | |
| record_insn_num); | |
| } | |
| else | |
| { | |
| printf_filtered (_("No instructions have been logged.\n")); | |
| } | |
| } | |
| else | |
| { | |
| printf_filtered (_("target record is not active.\n")); | |
| } | |
| /* Display max log size. */ | |
| printf_filtered (_("Max logged instructions is %d.\n"), | |
| record_insn_max_num); | |
| } | |
| /* Record log save-file format | |
| Version 1 (never released) | |
| Header: | |
| 4 bytes: magic number htonl(0x20090829). | |
| NOTE: be sure to change whenever this file format changes! | |
| Records: | |
| record_end: | |
| 1 byte: record type (record_end, see enum record_type). | |
| record_reg: | |
| 1 byte: record type (record_reg, see enum record_type). | |
| 8 bytes: register id (network byte order). | |
| MAX_REGISTER_SIZE bytes: register value. | |
| record_mem: | |
| 1 byte: record type (record_mem, see enum record_type). | |
| 8 bytes: memory length (network byte order). | |
| 8 bytes: memory address (network byte order). | |
| n bytes: memory value (n == memory length). | |
| Version 2 | |
| 4 bytes: magic number netorder32(0x20091016). | |
| NOTE: be sure to change whenever this file format changes! | |
| Records: | |
| record_end: | |
| 1 byte: record type (record_end, see enum record_type). | |
| 4 bytes: signal | |
| 4 bytes: instruction count | |
| record_reg: | |
| 1 byte: record type (record_reg, see enum record_type). | |
| 4 bytes: register id (network byte order). | |
| n bytes: register value (n == actual register size). | |
| (eg. 4 bytes for x86 general registers). | |
| record_mem: | |
| 1 byte: record type (record_mem, see enum record_type). | |
| 4 bytes: memory length (network byte order). | |
| 8 bytes: memory address (network byte order). | |
| n bytes: memory value (n == memory length). | |
| */ | |
| /* bfdcore_read -- read bytes from a core file section. */ | |
| static inline void | |
| bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset) | |
| { | |
| int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len); | |
| if (ret) | |
| *offset += len; | |
| else | |
| error (_("Failed to read %d bytes from core file %s ('%s').\n"), | |
| 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"); | |
| osec_size = bfd_section_size (core_bfd, osec); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "Find precord section %s.\n", | |
| osec ? "succeeded" : "failed"); | |
| if (osec == NULL) | |
| return; | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "%s", bfd_section_name (core_bfd, osec)); | |
| /* Check the magic code. */ | |
| bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset); | |
| if (magic != RECORD_FILE_MAGIC) | |
| error (_("Version mis-match or file format error in core file %s."), | |
| bfd_get_filename (core_bfd)); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "\ | |
| Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%s)\n", | |
| phex_nz (netorder32 (magic), 4)); | |
| /* Restore the entries in recfd into record_arch_list_head and | |
| record_arch_list_tail. */ | |
| record_arch_list_head = NULL; | |
| record_arch_list_tail = NULL; | |
| record_insn_num = 0; | |
| old_cleanups = make_cleanup (record_arch_list_cleanups, 0); | |
| regcache = get_current_regcache (); | |
| while (1) | |
| { | |
| uint8_t rectype; | |
| uint32_t regnum, len, signal, count; | |
| uint64_t addr; | |
| /* We are finished when offset reaches osec_size. */ | |
| if (bfd_offset >= osec_size) | |
| break; | |
| bfdcore_read (core_bfd, osec, &rectype, sizeof (rectype), &bfd_offset); | |
| switch (rectype) | |
| { | |
| case record_reg: /* reg */ | |
| /* Get register number to regnum. */ | |
| bfdcore_read (core_bfd, osec, ®num, | |
| sizeof (regnum), &bfd_offset); | |
| regnum = netorder32 (regnum); | |
| rec = record_reg_alloc (regcache, regnum); | |
| /* Get val. */ | |
| bfdcore_read (core_bfd, osec, record_get_loc (rec), | |
| rec->u.reg.len, &bfd_offset); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "\ | |
| Reading register %d (1 plus %lu plus %d bytes)\n", | |
| rec->u.reg.num, | |
| (unsigned long) sizeof (regnum), | |
| rec->u.reg.len); | |
| break; | |
| case record_mem: /* mem */ | |
| /* Get len. */ | |
| bfdcore_read (core_bfd, osec, &len, | |
| sizeof (len), &bfd_offset); | |
| len = netorder32 (len); | |
| /* Get addr. */ | |
| bfdcore_read (core_bfd, osec, &addr, | |
| sizeof (addr), &bfd_offset); | |
| addr = netorder64 (addr); | |
| rec = record_mem_alloc (addr, len); | |
| /* Get val. */ | |
| bfdcore_read (core_bfd, osec, record_get_loc (rec), | |
| rec->u.mem.len, &bfd_offset); | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "\ | |
| Reading memory %s (1 plus %lu plus %lu plus %d bytes)\n", | |
| paddress (get_current_arch (), | |
| rec->u.mem.addr), | |
| (unsigned long) sizeof (addr), | |
| (unsigned long) sizeof (len), | |
| rec->u.mem.len); | |
| break; | |
| case record_end: /* end */ | |
| rec = record_end_alloc (); | |
| record_insn_num ++; | |
| /* Get signal value. */ | |
| bfdcore_read (core_bfd, osec, &signal, | |
| sizeof (signal), &bfd_offset); | |
| signal = netorder32 (signal); | |
| rec->u.end.sigval = signal; | |
| /* Get insn count. */ | |
| bfdcore_read (core_bfd, osec, &count, | |
| sizeof (count), &bfd_offset); | |
| count = netorder32 (count); | |
| rec->u.end.insn_num = count; | |
| record_insn_count = count + 1; | |
| if (record_debug) | |
| fprintf_unfiltered (gdb_stdlog, "\ | |
| Reading record_end (1 + %lu + %lu bytes), offset == %s\n", | |
| (unsigned long) sizeof (signal), | |
| (unsigned long) sizeof (count), | |
| paddress (get_current_arch (), | |
| bfd_offset)); | |
| break; | |
| default: | |
| error (_("Bad entry type in core file %s."), | |
| bfd_get_filename (core_bfd)); | |
| break; | |
| } | |
| /* Add rec to record arch list. */ | |
| record_arch_list_add (rec); | |
| } | |
| 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').\n"), | |
| 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; | |
| 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); | |
| c = add_prefix_cmd ("record", class_obscure, cmd_record_start, | |
| _("Abbreviated form of \"target record\" command."), | |
| &record_cmdlist, "record ", 0, &cmdlist); | |
| set_cmd_completer (c, filename_completer); | |
| add_com_alias ("rec", "record", class_obscure, 1); | |
| add_prefix_cmd ("record", class_support, set_record_command, | |
| _("Set record options"), &set_record_cmdlist, | |
| "set record ", 0, &setlist); | |
| add_alias_cmd ("rec", "record", class_obscure, 1, &setlist); | |
| add_prefix_cmd ("record", class_support, show_record_command, | |
| _("Show record options"), &show_record_cmdlist, | |
| "show record ", 0, &showlist); | |
| add_alias_cmd ("rec", "record", class_obscure, 1, &showlist); | |
| add_prefix_cmd ("record", class_support, info_record_command, | |
| _("Info record options"), &info_record_cmdlist, | |
| "info record ", 0, &infolist); | |
| add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); | |
| 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.<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); | |
| add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist); | |
| add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist); | |
| add_cmd ("stop", class_obscure, cmd_record_stop, | |
| _("Stop the record/replay target."), | |
| &record_cmdlist); | |
| add_alias_cmd ("s", "stop", class_obscure, 1, &record_cmdlist); | |
| /* Record instructions number limit command. */ | |
| add_setshow_boolean_cmd ("stop-at-limit", no_class, | |
| &record_stop_at_limit, _("\ | |
| Set whether record/replay stops when record/replay buffer becomes full."), _("\ | |
| Show whether record/replay stops when record/replay buffer becomes full."), _("\ | |
| Default is ON.\n\ | |
| When ON, if the record/replay buffer becomes full, ask user what to do.\n\ | |
| When OFF, if the record/replay buffer becomes full,\n\ | |
| delete the oldest recorded instruction to make room for each new one."), | |
| NULL, NULL, | |
| &set_record_cmdlist, &show_record_cmdlist); | |
| add_setshow_uinteger_cmd ("insn-number-max", no_class, | |
| &record_insn_max_num, | |
| _("Set record/replay buffer limit."), | |
| _("Show record/replay buffer limit."), _("\ | |
| Set the maximum number of instructions to be stored in the\n\ | |
| record/replay buffer. Zero means unlimited. Default is 200000."), | |
| set_record_insn_max_num, | |
| NULL, &set_record_cmdlist, &show_record_cmdlist); | |
| add_cmd ("goto", class_obscure, cmd_record_goto, _("\ | |
| 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); | |
| } |