/* Process record and replay target for GDB, the GNU debugger. Copyright (C) 2008, 2009 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 . */ #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 #include #include #include "elf-bfd.h" /* For dump/load commands. */ #include "gcore.h" #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) #define RECORD_FILE_MAGIC htonl(0x20090726) /* These are the core struct of record function. An 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 has a struct record_entry ("record_end") that points out this is the last struct record_entry of this instruction. Each struct record_entry is linked to "record_list" by "prev" and "next". */ struct record_reg_entry { int num; gdb_byte *val; }; 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; gdb_byte *val; }; enum record_type { record_end = 0, record_reg, record_mem }; 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; } u; }; struct record_core_buf_entry { struct record_core_buf_entry *prev; struct target_section *p; bfd_byte *buf; }; /* This is the debug switch for process record. */ int record_debug = 0; /* 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; /* These list is for 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; static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; static int record_insn_num = 0; /* 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 void record_list_release (struct record_entry *rec) { struct record_entry *tmp; if (!rec) return; while (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); } if (rec != &record_first) xfree (rec); record_insn_num = 0; record_list = &record_first; record_list->next = NULL; record_arch_list_tail = NULL; record_arch_list_tail = NULL; } static void record_list_release_next (void) { struct record_entry *rec = record_list; struct record_entry *tmp = rec->next; rec->next = NULL; while (tmp) { rec = tmp->next; if (tmp->type == record_reg) 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); tmp = rec; } } static void record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; if (!record_first.next) return; 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); tmp = record_first.next; record_first.next = tmp->next; xfree (tmp); if (!record_first.next) { gdb_assert (record_insn_num == 1); break; } record_first.next->prev = &record_first; if (type == record_end) break; } record_insn_num--; } /* 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; } } /* Record the value of a register NUM to record_arch_list. */ int record_arch_list_add_reg (struct regcache *regcache, int num) { struct record_entry *rec; if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: add register num = %d to " "record list.\n", num); 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; regcache_raw_read (regcache, num, rec->u.reg.val); 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) 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; if (target_read_memory (addr, rec->u.mem.val, 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); 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 = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->prev = NULL; rec->next = NULL; rec->type = record_end; 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: stoped 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 (void *args) { int ret; struct regcache *regcache = args; 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); ret = gdbarch_process_record (get_regcache_arch (regcache), regcache, regcache_read_pc (regcache)); 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_insn (); else record_insn_num++; return 1; } static int do_record_message (struct regcache *regcache) { return catch_errors (record_message, regcache, 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; } static inline void record_exec_entry (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, entry->u.reg.val); memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); } break; case record_mem: /* mem */ { if (!record_list->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), record_list->u.mem.len); if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) { record_list->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, entry->u.mem.val, entry->u.mem.len)) { record_list->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); } } memcpy (entry->u.mem.val, mem, entry->u.mem.len); } } break; } } /* bfdcore_read -- read bytes from a core file section. */ static int 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; return ret; } /* Load the execution log from a file. */ static void record_load (void) { int recfd; uint32_t magic; struct cleanup *old_cleanups2; struct record_entry *rec; int insn_number = 0; asection *osec; void nullify_last_target_wait_ptid (void); /* We load the execution log from the open core bfd, if there is one. */ if (core_bfd == NULL) return; /* Open the load file. */ if (record_debug) fprintf_filtered (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"); printf_filtered ("Find precord section %s.\n", osec ? "succeeded" : "failed"); if (osec) { int i, len; int bfd_offset = 0; if (record_debug) fprintf_filtered (gdb_stdlog, "osec name = '%s'\n", bfd_section_name (core_bfd, osec)); len = (int) bfd_section_size (core_bfd, osec); printf_filtered ("osec size = %d\n", len); /* Check the magic code. */ if (!bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset)) error (_("Failed to read 'magic' from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (magic != RECORD_FILE_MAGIC) error (_("version mis-match / file format error.")); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), magic); /* Free any existing record log, and load the entries in core_bfd to the new record log. */ record_list_release (record_arch_list_tail); old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); while (1) { uint8_t tmpu8; uint64_t tmpu64; /* FIXME: Check offset for end-of-section. */ if (!bfdcore_read (core_bfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset)) break; switch (tmpu8) { case record_reg: /* reg */ /* FIXME: abstract out into an 'insert' function. */ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE); rec->prev = NULL; rec->next = NULL; rec->type = record_reg; /* Get num. */ /* FIXME: register num does not need 8 bytes. */ if (!bfdcore_read (core_bfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to read regnum from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); rec->u.reg.num = tmpu64; /* Get val. */ if (!bfdcore_read (core_bfd, osec, rec->u.reg.val, MAX_REGISTER_SIZE, &bfd_offset)) error (_("Failed to read regval from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"), rec->u.reg.num, *(ULONGEST *) rec->u.reg.val, MAX_REGISTER_SIZE); record_arch_list_add (rec); break; case record_mem: /* mem */ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->prev = NULL; rec->next = NULL; rec->type = record_mem; /* Get addr. */ if (!bfdcore_read (core_bfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to read memaddr from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); rec->u.mem.addr = tmpu64; /* Get len. */ /* FIXME: len does not need 8 bytes. */ if (!bfdcore_read (core_bfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to read memlen from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); rec->u.mem.len = tmpu64; rec->u.mem.mem_entry_not_accessible = 0; rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); /* Get val. */ if (!bfdcore_read (core_bfd, osec, rec->u.mem.val, rec->u.mem.len, &bfd_offset)) error (_("Failed to read memval from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Reading memory 0x%08x (1 plus 8 plus %d bytes)\n"), (unsigned int) rec->u.mem.addr, rec->u.mem.len); record_arch_list_add (rec); break; case record_end: /* end */ /* FIXME: restore the contents of record_end rec. */ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->prev = NULL; rec->next = NULL; rec->type = record_end; if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Reading record_end (one byte)\n")); record_arch_list_add (rec); insn_number ++; break; default: error (_("Format of core file is not right.")); break; } } } discard_cleanups (old_cleanups2); /* Add record_arch_list_head to the end of record list. (??? FIXME)*/ for (rec = record_list; rec->next; rec = rec->next) ; rec->next = record_arch_list_head; record_arch_list_head->prev = rec; /* Update record_insn_num and record_insn_max_num. */ record_insn_num = insn_number; 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. */ fprintf_filtered (gdb_stdout, "Loaded records from core file.\n"); registers_changed (); reinit_frame_cache (); nullify_last_target_wait_ptid (); print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); } 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 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; if (!name || (name && !*name)) error (_("Argument for gdb record filename required.\n")); /* 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); } static void record_open_1 (char *name, int from_tty) { struct target_ops *t; /* 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 (_("Process record can't get to_resume.")); if (!tmp_to_wait) error (_("Process record can't get to_wait.")); if (!tmp_to_store_registers) error (_("Process record can't get to_store_registers.")); if (!tmp_to_insert_breakpoint) error (_("Process record can't get to_insert_breakpoint.")); if (!tmp_to_remove_breakpoint) error (_("Process record can't get to_remove_breakpoint.")); push_target (&record_ops); } 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) { if (!nquery (_("Process record target already running, do you want to delete " "the old record log?"))) return; } /* 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; /* 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_xfer_partial) error (_("Process record can't get to_xfer_partial.")); if (current_target.to_stratum == core_stratum) record_core_open_1 (name, from_tty); else record_open_1 (name, from_tty); /* Reset */ record_insn_num = 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; /* Load record log if corefile contains one. */ record_load (); } 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; static enum target_signal record_resume_siggnal; static int record_resume_error; static void record_resume (struct target_ops *ops, ptid_t ptid, int step, enum target_signal siggnal) { record_resume_step = step; record_resume_siggnal = siggnal; if (!RECORD_IS_REPLAY) { if (do_record_message (get_current_regcache ())) { record_resume_error = 0; } else { record_resume_error = 1; return; } record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, siggnal); } } static int record_get_sig = 0; 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; } /* In replay mode, this function examines the recorded log 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); if (!RECORD_IS_REPLAY && ops != &record_core_ops) { 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; } if (record_resume_step) { /* This is a single step. */ return record_beneath_to_wait (record_beneath_to_wait_ops, ptid, status, 0); } 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, 0); if (status->kind == TARGET_WAITKIND_STOPPED && status->value.sig == TARGET_SIGNAL_TRAP) { /* Check if there is a breakpoint. */ registers_changed (); tmp_pc = regcache_read_pc (get_current_regcache ()); if (breakpoint_inserted_here_p (tmp_pc)) { /* There is a breakpoint. */ CORE_ADDR decr_pc_after_break = gdbarch_decr_pc_after_break (get_regcache_arch (get_current_regcache ())); if (decr_pc_after_break) { regcache_write_pc (get_thread_regcache (ret), tmp_pc + decr_pc_after_break); } } else { /* There is not a breakpoint. */ if (!do_record_message (get_current_regcache ())) { break; } record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, record_resume_siggnal); 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); int continue_flag = 1; int first_record_end = 1; struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); CORE_ADDR tmp_pc; 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 (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) regcache_write_pc (regcache, tmp_pc + gdbarch_decr_pc_after_break (gdbarch)); 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. 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_entry (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 (tmp_pc)) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: break " "at %s.\n", paddress (gdbarch, tmp_pc)); if (gdbarch_decr_pc_after_break (gdbarch) && execution_direction == EXEC_FORWARD && !record_resume_step) regcache_write_pc (regcache, tmp_pc + gdbarch_decr_pc_after_break (gdbarch)); 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); signal (SIGINT, handle_sigint); replay_out: if (record_get_sig) status->value.sig = TARGET_SIGNAL_INT; else status->value.sig = TARGET_SIGNAL_TRAP; discard_cleanups (old_cleanups); } do_cleanups (set_cleanups); return inferior_ptid; } 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); } 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); } 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_insn (); else record_insn_num++; } 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 = nquery (_("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 = nquery (_("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_next (); } record_registers_change (regcache, regno); } record_beneath_to_store_registers (record_beneath_to_store_registers_ops, regcache, regno); } /* 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 (!nquery (_("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_next (); } /* 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_insn (); 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; } 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; } static int record_can_execute_reverse (void) { return 1; } 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_can_execute_reverse = record_can_execute_reverse; record_ops.to_stratum = record_stratum; record_ops.to_magic = OPS_MAGIC; } static void record_core_resume (struct target_ops *ops, ptid_t ptid, int step, enum target_signal siggnal) { record_resume_step = step; record_resume_siggnal = siggnal; } 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); } 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); } static void record_core_prepare_to_store (struct regcache *regcache) { } 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.")); } 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; if (offset >= p->endaddr) continue; if (offset + len > p->endaddr) len = p->endaddr - 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 + 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 + 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); } static int record_core_insert_breakpoint (struct gdbarch *gdbarch, struct bp_target_info *bp_tgt) { return 0; } static int record_core_remove_breakpoint (struct gdbarch *gdbarch, struct bp_target_info *bp_tgt) { return 0; } 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_can_execute_reverse = record_can_execute_reverse; record_core_ops.to_has_execution = record_core_has_execution; record_core_ops.to_stratum = record_stratum; record_core_ops.to_magic = OPS_MAGIC; } 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); } static void cmd_record_load (char *args, int from_tty) { char buf[512]; snprintf (buf, 512, "target record %s", args); execute_command (buf, from_tty); } /* Record log save-file format Version 1 Header: 4 bytes: magic number htonl(0x20090726). NOTE: be sure to change whenever this file format changes! Records: record_end: 1 byte: record type (record_end). record_reg: 1 byte: record type (record_reg). 8 bytes: register id (network byte order). MAX_REGISTER_SIZE bytes: register value. record_mem: 1 byte: record type (record_mem). 8 bytes: memory address (network byte order). 8 bytes: memory length (network byte order). n bytes: memory value (n == memory length). */ /* bfdcore_write -- write bytes into a core file section. */ static int 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; return ret; } /* Dump the execution log to a file. */ static void cmd_record_dump (char *args, int from_tty) { char *recfilename, recfilename_buffer[40]; int recfd; 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 dump_size = 0; asection *osec = NULL; struct record_entry *p; int bfd_offset = 0; if (current_target.to_stratum != record_stratum) error (_("Process record is not started.\n")); if (args && *args) recfilename = args; else { /* Default recfile name is "rec.PID". */ snprintf (recfilename_buffer, sizeof (recfilename_buffer), "gdb_record.%d", PIDGET (inferior_ptid)); recfilename = recfilename_buffer; } /* Open the dump file. */ if (record_debug) fprintf_filtered (gdb_stdlog, _("Saving recording to file '%s'\n"), recfilename); /* Open the output file. */ obfd = create_gcore_bfd (recfilename); /* Need a cleanup that will close the file (FIXME: delete it?). */ old_cleanups = make_cleanup_bfd_close (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. */ for (; record_list && record_list != &record_first; record_list = record_list->prev) record_exec_entry (regcache, gdbarch, record_list); /* Compute the size needed for the extra bfd section. */ dump_size = 4; /* magic cookie */ for (p = &record_first; p; p = p->next) switch (p->type) { case record_end: dump_size += 1; break; case record_reg: dump_size += 1 + 8 + MAX_REGISTER_SIZE; break; case record_mem: dump_size += 1 + 8 + 8 + p->u.mem.len; break; } /* Make the new bfd section. */ osec = bfd_make_section_anyway (obfd, "precord"); bfd_set_section_size (obfd, osec, dump_size); bfd_set_section_vma (obfd, osec, 0); bfd_section_lma (obfd, osec) = 0; bfd_set_section_flags (obfd, osec, SEC_ALLOC | SEC_HAS_CONTENTS); /* Save corefile state. */ write_gcore_file (obfd); /* Write out the record log (modified Hui method). */ /* Write the magic code. */ magic = RECORD_FILE_MAGIC; if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), magic); if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset)) error (_("Failed to write 'magic' to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); /* Dump the entries into the new bfd section. */ for (p = &record_first; p; p = p->next) { uint8_t tmpu8; uint64_t tmpu64; tmpu8 = p->type; if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset)) error (_("Failed to write 'type' to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); switch (p->type) { case record_reg: /* reg */ tmpu64 = p->u.reg.num; if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Writing register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"), p->u.reg.num, *(ULONGEST *) p->u.reg.val, MAX_REGISTER_SIZE); /* FIXME: register num does not need 8 bytes. */ if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to write regnum to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); /* FIXME: add a len field, and write the smaller value. */ if (!bfdcore_write (obfd, osec, p->u.reg.val, MAX_REGISTER_SIZE, &bfd_offset)) error (_("Failed to write regval to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); break; case record_mem: /* mem */ tmpu64 = p->u.mem.addr; if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Writing memory 0x%08x (1 plus 8 plus 8 bytes plus %d bytes)\n"), (unsigned int) p->u.mem.addr, p->u.mem.len); if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to write memaddr to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); tmpu64 = p->u.mem.len; if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); /* FIXME: len does not need 8 bytes. */ if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to write memlen to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); if (!bfdcore_write (obfd, osec, p->u.mem.val, p->u.mem.len, &bfd_offset)) error (_("Failed to write memval to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); break; case record_end: /* FIXME: record the contents of record_end rec. */ if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Writing record_end (1 byte)\n")); break; } } /* Now forward-execute back to the saved entry. */ for (record_list = &record_first; record_list && record_list != cur_record_list; record_list = record_list->next) record_exec_entry (regcache, gdbarch, record_list); /* Clean-ups will close the output file and free malloc memory. */ do_cleanups (old_cleanups); /* Succeeded. */ fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename); } /* 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_next (); } else printf_unfiltered (_("Already at end of record list.\n")); } else printf_unfiltered (_("Process record is not started.\n")); } /* Implement the "stoprecord" command. */ static void cmd_record_stop (char *args, int from_tty) { if (current_target.to_stratum == record_stratum) { if (!record_list || !from_tty || query (_("Delete recorded log and " "stop recording?"))) { if (strcmp (current_target.to_shortname, "record") == 0) unpush_target (&record_ops); else unpush_target (&record_core_ops); } } 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) { printf_unfiltered (_("Record instructions number is bigger than " "record instructions max number. Auto delete " "the first ones?\n")); while (record_insn_num > record_insn_max_num) record_list_release_first_insn (); } } /* 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")); 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, ""); } static void info_record_command (char *args, int from_tty) { cmd_show_list (info_record_cmdlist, from_tty, ""); } 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 ("load", class_obscure, cmd_record_load, _("Load previously dumped execution records from \ a file given as argument."), &record_cmdlist); set_cmd_completer (c, filename_completer); c = add_cmd ("dump", class_obscure, cmd_record_dump, _("Dump the execution records to a file.\n\ Argument is optional filename. Default filename is 'gdb_record.'."), &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_zinteger_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 ("insn-number", class_obscure, show_record_insn_number, _("Show the current number of instructions in the " "record/replay buffer."), &info_record_cmdlist); }