/* Process record and replay target for GDB, the GNU debugger.
- Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+ Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
This file is part of GDB.
#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>
#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
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. */
if (record_stop_at_limit)
{
int q;
+
if (set_terminal)
target_terminal_ours ();
q = yquery (_("Do you want to auto delete previous execution "
if (q)
record_stop_at_limit = 0;
else
- error (_("Process record: inferior program stopped."));
+ error (_("Process record: stopped by user."));
}
}
}
}
-/* Before inferior step (when GDB record the running message, inferior
- only can step), GDB will call this function to record the values to
- record_list. This function will call gdbarch_process_record to
- record the running message of inferior and set them to
- record_arch_list, and add it to record_list. */
-
static void
-record_message_cleanups (void *ignore)
+record_arch_list_cleanups (void *ignore)
{
record_list_release (record_arch_list_tail);
}
-struct record_message_args {
- struct regcache *regcache;
- enum target_signal signal;
-};
+/* Before inferior step (when GDB record the running message, inferior
+ only can step), GDB will call this function to record the values to
+ record_list. This function will call gdbarch_process_record to
+ record the running message of inferior and set them to
+ record_arch_list, and add it to record_list. */
static int
-record_message (void *args)
+record_message (struct regcache *regcache, enum target_signal signal)
{
int ret;
- struct record_message_args *myargs = args;
- struct gdbarch *gdbarch = get_regcache_arch (myargs->regcache);
- struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+ struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
record_arch_list_head = NULL;
record_arch_list_tail = NULL;
if (record_list != &record_first) /* FIXME better way to check */
{
gdb_assert (record_list->type == record_end);
- record_list->u.end.sigval = myargs->signal;
+ record_list->u.end.sigval = signal;
}
- if (myargs->signal == TARGET_SIGNAL_0
+ if (signal == TARGET_SIGNAL_0
|| !gdbarch_process_record_signal_p (gdbarch))
ret = gdbarch_process_record (gdbarch,
- myargs->regcache,
- regcache_read_pc (myargs->regcache));
+ regcache,
+ regcache_read_pc (regcache));
else
ret = gdbarch_process_record_signal (gdbarch,
- myargs->regcache,
- myargs->signal);
+ regcache,
+ signal);
if (ret > 0)
error (_("Process record: inferior program stopped."));
return 1;
}
+struct record_message_args {
+ struct regcache *regcache;
+ enum target_signal signal;
+};
+
+static int
+record_message_wrapper (void *args)
+{
+ struct record_message_args *record_args = args;
+
+ return record_message (record_args->regcache, record_args->signal);
+}
+
static int
-do_record_message (struct regcache *regcache,
- enum target_signal signal)
+record_message_wrapper_safe (struct regcache *regcache,
+ enum target_signal signal)
{
struct record_message_args args;
args.regcache = regcache;
args.signal = signal;
- return catch_errors (record_message, &args, NULL, RETURN_MASK_ALL);
+
+ return catch_errors (record_message_wrapper, &args, NULL, RETURN_MASK_ALL);
}
/* Set to 1 if record_store_registers and record_xfer_partial
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. */
{
entry->u.mem.mem_entry_not_accessible = 1;
if (record_debug)
- warning (_("Process record: error reading memory at "
- "addr = %s len = %d."),
+ warning ("Process record: error reading memory at "
+ "addr = %s len = %d.",
paddress (gdbarch, entry->u.mem.addr),
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."),
+ 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);
+ {
+ 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;
+ }
}
}
}
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. */
}
push_target (&record_core_ops);
+ record_restore ();
}
/* "to_open" target method for 'live' processes. */
static void
record_open_1 (char *name, int from_tty)
{
- struct target_ops *t;
-
if (record_debug)
fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
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);
}
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)
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."));
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);
}
static int record_resume_step = 0;
-static int record_resume_error;
/* "to_resume" target method. Resume the process record target. */
if (!RECORD_IS_REPLAY)
{
- if (do_record_message (get_current_regcache (), signal))
- {
- record_resume_error = 0;
- }
- else
- {
- record_resume_error = 1;
- return;
- }
+ record_message (get_current_regcache (), signal);
record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1,
signal);
}
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. */
&& 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. */
- /* Yes -- check if there is a breakpoint. */
registers_changed ();
regcache = get_current_regcache ();
tmp_pc = regcache_read_pc (regcache);
- if (breakpoint_inserted_here_p (get_regcache_aspace (regcache),
- tmp_pc))
+ aspace = get_regcache_aspace (regcache);
+
+ if (target_stopped_by_watchpoint ())
{
- /* There is a breakpoint. GDB will want to stop. */
- 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);
+ /* 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
{
- /* There is not a breakpoint, and gdb is not
- stepping, therefore gdb will not stop.
- Therefore we will not return to gdb.
- Record the insn and resume. */
- if (!do_record_message (regcache, TARGET_SIGNAL_0))
- break;
+ /* 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,
{
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 (get_regcache_aspace (regcache),
- tmp_pc))
+ if (breakpoint_inserted_here_p (aspace, tmp_pc))
{
+ int decr_pc_after_break = gdbarch_decr_pc_after_break (gdbarch);
+
if (record_debug)
fprintf_unfiltered (gdb_stdlog,
"Process record: break at %s.\n",
paddress (gdbarch, tmp_pc));
- if (gdbarch_decr_pc_after_break (gdbarch)
- && !record_resume_step)
+
+ if (decr_pc_after_break
+ && !record_resume_step
+ && software_breakpoint_inserted_here_p (aspace, tmp_pc))
regcache_write_pc (regcache,
- tmp_pc +
- gdbarch_decr_pc_after_break (gdbarch));
+ tmp_pc + decr_pc_after_break);
goto replay_out;
}
}
/* check breakpoint */
tmp_pc = regcache_read_pc (regcache);
- if (breakpoint_inserted_here_p (get_regcache_aspace (regcache),
- tmp_pc))
+ if (breakpoint_inserted_here_p (aspace, tmp_pc))
{
+ int decr_pc_after_break
+ = gdbarch_decr_pc_after_break (gdbarch);
+
if (record_debug)
fprintf_unfiltered (gdb_stdlog,
"Process record: break "
"at %s.\n",
paddress (gdbarch, tmp_pc));
- if (gdbarch_decr_pc_after_break (gdbarch)
+ if (decr_pc_after_break
&& execution_direction == EXEC_FORWARD
- && !record_resume_step)
+ && !record_resume_step
+ && software_breakpoint_inserted_here_p (aspace,
+ tmp_pc))
regcache_write_pc (regcache,
- tmp_pc +
- gdbarch_decr_pc_after_break (gdbarch));
+ tmp_pc + decr_pc_after_break);
+ continue_flag = 0;
+ }
+
+ if (record_hw_watchpoint)
+ {
+ if (record_debug)
+ fprintf_unfiltered (gdb_stdlog, "\
+Process record: hit hw watchpoint.\n");
continue_flag = 0;
}
/* Check target signal */
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
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))
if (regno < 0)
{
int i;
+
for (i = 0;
i < gdbarch_num_regs (get_regcache_arch (regcache));
i++)
record_list_release (record_arch_list_tail);
if (record_debug)
fprintf_unfiltered (gdb_stdlog,
- _("Process record: failed to record "
- "execution log."));
+ "Process record: failed to record "
+ "execution log.");
return -1;
}
if (record_arch_list_add_end ())
record_list_release (record_arch_list_tail);
if (record_debug)
fprintf_unfiltered (gdb_stdlog,
- _("Process record: failed to record "
- "execution log."));
+ "Process record: failed to record "
+ "execution log.");
return -1;
}
record_list->next = record_arch_list_head;
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_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;
}
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."));
- }
+ 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,
static void
init_record_core_ops (void)
{
- record_core_ops.to_shortname = "record_core";
+ 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_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;
}
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;
NULL, show_record_debug, &setdebuglist,
&showdebuglist);
- add_prefix_cmd ("record", class_obscure, cmd_record_start,
- _("Abbreviated form of \"target record\" command."),
- &record_cmdlist, "record ", 0, &cmdlist);
+ c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
+ _("Abbreviated form of \"target record\" command."),
+ &record_cmdlist, "record ", 0, &cmdlist);
+ set_cmd_completer (c, filename_completer);
+
add_com_alias ("rec", "record", class_obscure, 1);
add_prefix_cmd ("record", class_support, set_record_command,
_("Set record options"), &set_record_cmdlist,
"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/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);
}