mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
record_full_arch_list_add moves its `rec` argument, so it should take an rvalue ref to reflect that. Otherwise the fact that the object is moved from can be a "surprise" to the callers. Change-Id: Ibeb56b1523ad8aee077d3bc3cad1f00d6b47ea2f Approved-By: Guinevere Larsen <guinevere@redhat.com>
2956 lines
82 KiB
C
2956 lines
82 KiB
C
/* Process record and replay target for GDB, the GNU debugger.
|
|
|
|
Copyright (C) 2013-2026 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 "exceptions.h"
|
|
#include "extract-store-integer.h"
|
|
#include "cli/cli-cmds.h"
|
|
#include "gdbsupport/gdb_vecs.h"
|
|
#include "regcache.h"
|
|
#include "gdbthread.h"
|
|
#include "inferior.h"
|
|
#include "event-top.h"
|
|
#include "completer.h"
|
|
#include "arch-utils.h"
|
|
#include "gdbcore.h"
|
|
#include "exec.h"
|
|
#include "record.h"
|
|
#include "record-full.h"
|
|
#include "elf-bfd.h"
|
|
#include "gcore.h"
|
|
#include "gdbsupport/event-loop.h"
|
|
#include "inf-loop.h"
|
|
#include "gdb_bfd.h"
|
|
#include "observable.h"
|
|
#include "infrun.h"
|
|
#include "gdbsupport/gdb_unlinker.h"
|
|
#include "gdbsupport/byte-vector.h"
|
|
#include "gdbsupport/scoped_signal_handler.h"
|
|
#include "async-event.h"
|
|
#include "top.h"
|
|
#include "valprint.h"
|
|
#include "interps.h"
|
|
#include "cli/cli-style.h"
|
|
|
|
#include <vector>
|
|
#include <optional>
|
|
#include <deque>
|
|
#include <signal.h>
|
|
#include <variant>
|
|
|
|
/* This module implements "target record-full", 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 resume and 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 degradation).
|
|
|
|
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_FULL_INSN_MAX_NUM 200000
|
|
|
|
#define RECORD_FULL_IS_REPLAY \
|
|
((record_full_next_insn != record_full_log.size ()) \
|
|
|| ::execution_direction == EXEC_REVERSE)
|
|
|
|
#define RECORD_FULL_FILE_MAGIC_OLD netorder32(0x20091016)
|
|
#define RECORD_FULL_FILE_MAGIC netorder32(0x20260415)
|
|
|
|
/* These are the core structs of the process record functionality.
|
|
|
|
A record_full_entry is a record of the value change of a register
|
|
("record_full_reg") or a part of memory ("record_full_mem").
|
|
These are saved on the record_full_instruction struct, which also
|
|
contains some extra information, such as delivered signals. */
|
|
|
|
struct record_full_mem_entry
|
|
{
|
|
CORE_ADDR addr;
|
|
int len;
|
|
/* Set this flag if target memory for this entry
|
|
can be accessed. */
|
|
bool mem_entry_accessible;
|
|
union
|
|
{
|
|
gdb_byte *ptr;
|
|
gdb_byte buf[sizeof (gdb_byte *)];
|
|
} u;
|
|
|
|
record_full_mem_entry () : addr (0), len (0), mem_entry_accessible (true)
|
|
{ }
|
|
|
|
record_full_mem_entry (CORE_ADDR mem_addr, int mem_len)
|
|
{
|
|
addr = mem_addr;
|
|
len = mem_len;
|
|
if (len > sizeof (u.buf))
|
|
u.ptr = new gdb_byte[len];
|
|
mem_entry_accessible = true;
|
|
}
|
|
|
|
record_full_mem_entry (record_full_mem_entry &&other)
|
|
{
|
|
addr = other.addr;
|
|
len = other.len;
|
|
memcpy (u.buf, other.u.buf, sizeof (u.buf));
|
|
mem_entry_accessible = other.mem_entry_accessible;
|
|
/* With len == 0, OTHER is guaranteed to not try to free the
|
|
memory when it is destructed. */
|
|
other.len = 0;
|
|
}
|
|
|
|
~record_full_mem_entry ()
|
|
{
|
|
if (len > sizeof (u.buf))
|
|
delete[] u.ptr;
|
|
}
|
|
|
|
record_full_mem_entry &operator= (record_full_mem_entry &&other)
|
|
{
|
|
/* Since this struct can't be copied, there will only be one instance
|
|
of it at a time. Therefore, there isn't really a way to do
|
|
self-assignment, unless we actually do "entry = entry". */
|
|
gdb_assert (this != &other);
|
|
if (len > sizeof (u.buf))
|
|
free (u.ptr);
|
|
addr = other.addr;
|
|
len = other.len;
|
|
memcpy (u.buf, other.u.buf, sizeof (u.buf));
|
|
mem_entry_accessible = other.mem_entry_accessible;
|
|
/* With len == 0, OTHER is guaranteed to not try to free the
|
|
memory when it is destructed. */
|
|
other.len = 0;
|
|
return *this;
|
|
}
|
|
|
|
DISABLE_COPY_AND_ASSIGN (record_full_mem_entry);
|
|
|
|
/* Create a mem_entry from a bfd file, when restoring a recording. */
|
|
static record_full_mem_entry from_bfd (bfd *cbfd, asection* osec,
|
|
int *bfd_offset);
|
|
|
|
/* Save this mem entry to a bfd file. */
|
|
void to_bfd (const gdb_bfd_ref_ptr &obfd, asection *osec, int *bfd_offset,
|
|
gdbarch *gdbarch);
|
|
|
|
gdb_byte *get_loc ()
|
|
{
|
|
if (len > sizeof (u.buf))
|
|
return u.ptr;
|
|
else
|
|
return u.buf;
|
|
}
|
|
|
|
bool execute (gdbarch *gdbarch)
|
|
{
|
|
/* Nothing to do if the memory is flagged not_accessible. */
|
|
if (!mem_entry_accessible)
|
|
return false;
|
|
|
|
gdb::byte_vector buf (len);
|
|
|
|
if (record_debug > 1)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: record_full_mem %s to "
|
|
"inferior addr = %s len = %d.\n",
|
|
host_address_to_string (this),
|
|
paddress (gdbarch, addr),
|
|
len);
|
|
|
|
if (record_read_memory (gdbarch, addr, buf.data (), len))
|
|
mem_entry_accessible = false;
|
|
else
|
|
{
|
|
if (target_write_memory (addr,
|
|
get_loc (),
|
|
len))
|
|
{
|
|
mem_entry_accessible = false;
|
|
if (record_debug)
|
|
warning (_("Process record: error writing memory at "
|
|
"addr = %s len = %d."),
|
|
paddress (gdbarch, addr),
|
|
len);
|
|
}
|
|
else
|
|
{
|
|
memcpy (get_loc (), buf.data (), 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
|
|
(current_inferior ()->aspace.get (), addr, len))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct record_full_reg_entry
|
|
{
|
|
unsigned short num;
|
|
unsigned short len;
|
|
union
|
|
{
|
|
gdb_byte *ptr;
|
|
gdb_byte buf[2 * sizeof (gdb_byte *)];
|
|
} u;
|
|
|
|
record_full_reg_entry () : num (0), len (0) {
|
|
u.ptr = nullptr;
|
|
}
|
|
|
|
record_full_reg_entry (gdbarch *gdbarch, int regnum)
|
|
{
|
|
num = regnum;
|
|
len = register_size (gdbarch, regnum);
|
|
if (len > sizeof (u.buf))
|
|
u.ptr = new gdb_byte[len];
|
|
else
|
|
u.ptr = nullptr;
|
|
}
|
|
|
|
record_full_reg_entry (record_full_reg_entry &&other)
|
|
{
|
|
num = other.num;
|
|
len = other.len;
|
|
memcpy (u.buf, other.u.buf, sizeof (u.buf));
|
|
/* With len == 0, OTHER is guaranteed to not try to free the
|
|
memory when it is destructed. */
|
|
other.len = 0;
|
|
}
|
|
|
|
~record_full_reg_entry ()
|
|
{
|
|
if (len > sizeof (u.buf))
|
|
delete[] u.ptr;
|
|
}
|
|
|
|
record_full_reg_entry &operator= (record_full_reg_entry &&other)
|
|
{
|
|
/* Since this struct can't be copied, there will only be one instance
|
|
of it at a time. Therefore, there isn't really a way to do
|
|
self-assignment, unless we actually do "entry = entry".
|
|
Also, again because we can't copy it, if we are moving OTHER to a
|
|
pre-existing entry, we'll be losing information on a recorded or
|
|
partial instruction. So we should only assign when the entry is
|
|
uninitialized (len == 0). */
|
|
gdb_assert (this != &other);
|
|
if (len > sizeof (u.buf))
|
|
free (u.ptr);
|
|
num = other.num;
|
|
len = other.len;
|
|
memcpy (u.buf, other.u.buf, sizeof (u.buf));
|
|
/* With len == 0, OTHER is guaranteed to not try to free the
|
|
memory when it is destructed. */
|
|
other.len = 0;
|
|
return *this;
|
|
}
|
|
|
|
DISABLE_COPY_AND_ASSIGN (record_full_reg_entry);
|
|
|
|
/* Create a reg_entry from a bfd file, when restoring a recording. */
|
|
static record_full_reg_entry from_bfd (bfd *cbfd, asection* osec,
|
|
int *bfd_offset);
|
|
|
|
/* Save this reg entry to a bfd file. */
|
|
void to_bfd (const gdb_bfd_ref_ptr &obfd, asection *osec, int *bfd_offset);
|
|
|
|
gdb_byte *get_loc ()
|
|
{
|
|
if (len > sizeof (u.buf))
|
|
return u.ptr;
|
|
else
|
|
return u.buf;
|
|
}
|
|
|
|
bool execute (regcache *regcache)
|
|
{
|
|
gdb::byte_vector buf (len);
|
|
|
|
if (record_debug > 1)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: record_full_reg %s to "
|
|
"inferior num = %d.\n",
|
|
host_address_to_string (this),
|
|
num);
|
|
|
|
regcache->cooked_read (num, buf);
|
|
regcache->cooked_write (num, get_loc ());
|
|
memcpy (get_loc (), buf.data (), len);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
enum record_full_type
|
|
{
|
|
record_full_reg,
|
|
record_full_mem
|
|
};
|
|
|
|
class record_full_entry
|
|
{
|
|
std::variant<record_full_reg_entry, record_full_mem_entry> entry;
|
|
|
|
public:
|
|
record_full_entry () : entry (record_full_reg_entry ()) {}
|
|
|
|
/* Constructor for a register entry. Type is here to make it
|
|
easier to recognize it in the constructor calls, it isn't
|
|
actually important. */
|
|
record_full_entry (record_full_type reg_type, gdbarch *gdbarch,
|
|
int regnum)
|
|
: entry(record_full_reg_entry (gdbarch, regnum))
|
|
{
|
|
gdb_assert (reg_type == record_full_reg);
|
|
}
|
|
|
|
record_full_entry (record_full_type mem_type, CORE_ADDR addr, int len)
|
|
: entry(record_full_mem_entry (addr, len))
|
|
{
|
|
gdb_assert (mem_type == record_full_mem);
|
|
}
|
|
|
|
record_full_entry (record_full_entry &&other)
|
|
: entry (std::move (other.entry))
|
|
{
|
|
}
|
|
|
|
DISABLE_COPY_AND_ASSIGN (record_full_entry);
|
|
|
|
/* Create a generic entry from a bfd file, when restoring a recording. */
|
|
static void from_bfd (bfd *cbfd, asection* osec, int *bfd_offset);
|
|
|
|
/* Save this entry to a bfd file. */
|
|
void to_bfd (const gdb_bfd_ref_ptr &obfd, asection *osec, int *bfd_offset,
|
|
gdbarch *gdbarch);
|
|
|
|
record_full_reg_entry& reg ()
|
|
{
|
|
gdb_assert (type () == record_full_reg);
|
|
return std::get<record_full_reg_entry> (entry);
|
|
}
|
|
|
|
record_full_mem_entry& mem ()
|
|
{
|
|
gdb_assert (type () == record_full_mem);
|
|
return std::get<record_full_mem_entry> (entry);
|
|
}
|
|
|
|
record_full_type type () const
|
|
{
|
|
switch (entry.index ())
|
|
{
|
|
case 0:
|
|
return record_full_reg;
|
|
case 1:
|
|
return record_full_mem;
|
|
}
|
|
gdb_assert_not_reached ("Impossible variant index");
|
|
}
|
|
|
|
/* Get the pointer to the data stored by this entry. */
|
|
gdb_byte *get_loc ()
|
|
{
|
|
switch (type ())
|
|
{
|
|
case record_full_reg:
|
|
return reg ().get_loc ();
|
|
case record_full_mem:
|
|
return mem ().get_loc ();
|
|
}
|
|
gdb_assert_not_reached ("Impossible entry type");
|
|
}
|
|
|
|
/* Execute this entry, swapping the appropriate values from memory or
|
|
register and the recorded ones. Returns TRUE if the execution was
|
|
stopped by a watchpoint. */
|
|
|
|
bool execute (regcache *regcache)
|
|
{
|
|
switch (type ())
|
|
{
|
|
case record_full_reg:
|
|
return reg ().execute (regcache);
|
|
case record_full_mem:
|
|
return mem ().execute (regcache->arch ());
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/* This is the main structure that comprises the execution log.
|
|
Each instruction is comprised of:
|
|
* The instruction number: How many instructions were recorded before
|
|
this one;
|
|
* sigval: Whether the inferior received a signal while the following
|
|
instruction was being recorded;
|
|
* pc: The Program Counter at the start of the instruction;
|
|
* effects: A list of record_full_entry structures, each of which
|
|
describing one effect that the instruction has on the inferior.
|
|
|
|
Note, the signal is stored in the previous instruction for historical
|
|
reasons. This is how it was first implemented, and no one has gotten
|
|
around to changing it yet. */
|
|
|
|
struct record_full_instruction
|
|
{
|
|
/* This might be different from the index if
|
|
we had to remove the first few instructions. */
|
|
uint32_t insn_num;
|
|
std::optional<gdb_signal> sigval;
|
|
std::vector<record_full_entry> effects;
|
|
record_full_reg_entry pc;
|
|
|
|
/* Execute the full instruction. As a side effect, set
|
|
record_full_stop_reason. */
|
|
void exec_insn (regcache *regcache);
|
|
|
|
/* Create a full recorded instruction from a bfd. */
|
|
static void from_bfd (bfd *cbfd, asection* osec, int *bfd_offset);
|
|
|
|
/* Save this instruction to a bfd file. */
|
|
void to_bfd (gdb_bfd_ref_ptr &obfd, asection *osec, int *bfd_offset,
|
|
gdbarch *gdbarch);
|
|
};
|
|
|
|
/* If true, query if PREC cannot record memory
|
|
change of next instruction. */
|
|
bool record_full_memory_query = false;
|
|
|
|
struct record_full_core_buf_entry
|
|
{
|
|
struct record_full_core_buf_entry *prev;
|
|
struct target_section *p;
|
|
bfd_byte *buf;
|
|
};
|
|
|
|
/* Record buf with core target. */
|
|
static detached_regcache *record_full_core_regbuf = NULL;
|
|
static std::vector<target_section> record_full_core_sections;
|
|
static struct record_full_core_buf_entry *record_full_core_buf_list = NULL;
|
|
|
|
/* The following variables are used for managing the history of executed
|
|
instructions from the inferior.
|
|
|
|
record_full_log contains all instructions that were fully executed and
|
|
saved to the log, so that we can replay the execution.
|
|
|
|
record_full_next_insn always points to the next instruction that would
|
|
be executed if the inferior executes forward. In the special case when
|
|
the inferior is not replaying, record_full_next_insn points past the
|
|
end of the history.
|
|
|
|
record_full_incomplete_instruction holds a partial instruction, while
|
|
the lower target is disassembling the instruction, or as partial xfers are
|
|
happening. It is manipulated by the "arch list" functions for historical
|
|
reasons. */
|
|
|
|
static std::deque<record_full_instruction> record_full_log;
|
|
static record_full_instruction record_full_incomplete_instruction;
|
|
static int record_full_next_insn;
|
|
|
|
/* true ask user. false auto delete the last struct record_full_entry. */
|
|
static bool record_full_stop_at_limit = true;
|
|
/* Maximum allowed number of insns in execution log. */
|
|
static unsigned int record_full_insn_max_num
|
|
= DEFAULT_RECORD_FULL_INSN_MAX_NUM;
|
|
/* Count of insns logged so far (may be larger
|
|
than count of insns presently in execution log). */
|
|
static ULONGEST record_full_insn_count;
|
|
|
|
static const char record_longname[]
|
|
= N_("Process record and replay target");
|
|
static const char record_doc[]
|
|
= N_("Log program while executing and replay execution from log.");
|
|
|
|
/* Base class implementing functionality common to both the
|
|
"record-full" and "record-core" targets. */
|
|
|
|
class record_full_base_target : public target_ops
|
|
{
|
|
public:
|
|
const target_info &info () const override = 0;
|
|
|
|
strata stratum () const override { return record_stratum; }
|
|
|
|
void close () override;
|
|
void async (bool) override;
|
|
ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
|
|
bool stopped_by_watchpoint () override;
|
|
std::vector<CORE_ADDR> stopped_data_addresses () override;
|
|
|
|
bool stopped_by_sw_breakpoint () override;
|
|
bool supports_stopped_by_sw_breakpoint () override;
|
|
|
|
bool stopped_by_hw_breakpoint () override;
|
|
bool supports_stopped_by_hw_breakpoint () override;
|
|
|
|
bool can_execute_reverse () override;
|
|
|
|
/* Add bookmark target methods. */
|
|
gdb_byte *get_bookmark (const char *, int) override;
|
|
void goto_bookmark (const gdb_byte *, int) override;
|
|
enum exec_direction_kind execution_direction () override;
|
|
enum record_method record_method (ptid_t ptid) override;
|
|
void info_record () override;
|
|
void save_record (const char *filename) override;
|
|
bool supports_delete_record () override;
|
|
void delete_record () override;
|
|
bool record_is_replaying (ptid_t ptid) override;
|
|
bool record_will_replay (ptid_t ptid, int dir) override;
|
|
void record_stop_replaying () override;
|
|
void goto_record_begin () override;
|
|
void goto_record_end () override;
|
|
void goto_record (ULONGEST insn) override;
|
|
};
|
|
|
|
/* The "record-full" target. */
|
|
|
|
static const target_info record_full_target_info = {
|
|
"record-full",
|
|
record_longname,
|
|
record_doc,
|
|
};
|
|
|
|
class record_full_target final : public record_full_base_target
|
|
{
|
|
public:
|
|
const target_info &info () const override
|
|
{ return record_full_target_info; }
|
|
|
|
void resume (ptid_t, int, enum gdb_signal) override;
|
|
void disconnect (const char *, int) override;
|
|
void detach (inferior *, int) override;
|
|
void mourn_inferior () override;
|
|
void kill () override;
|
|
void store_registers (struct regcache *, int) override;
|
|
enum target_xfer_status xfer_partial (enum target_object object,
|
|
const char *annex,
|
|
gdb_byte *readbuf,
|
|
const gdb_byte *writebuf,
|
|
ULONGEST offset, ULONGEST len,
|
|
ULONGEST *xfered_len) override;
|
|
int insert_breakpoint (struct gdbarch *,
|
|
struct bp_target_info *) override;
|
|
int remove_breakpoint (struct gdbarch *,
|
|
struct bp_target_info *,
|
|
enum remove_bp_reason) override;
|
|
};
|
|
|
|
/* The "record-core" target. */
|
|
|
|
static const target_info record_full_core_target_info = {
|
|
"record-core",
|
|
record_longname,
|
|
N_("Load a saved execution log, allowing replaying the last instructions."),
|
|
};
|
|
|
|
class record_full_core_target final : public record_full_base_target
|
|
{
|
|
public:
|
|
const target_info &info () const override
|
|
{ return record_full_core_target_info; }
|
|
|
|
void resume (ptid_t, int, enum gdb_signal) override;
|
|
void disconnect (const char *, int) override;
|
|
void kill () override;
|
|
void fetch_registers (struct regcache *regcache, int regno) override;
|
|
void prepare_to_store (struct regcache *regcache) override;
|
|
void store_registers (struct regcache *, int) override;
|
|
enum target_xfer_status xfer_partial (enum target_object object,
|
|
const char *annex,
|
|
gdb_byte *readbuf,
|
|
const gdb_byte *writebuf,
|
|
ULONGEST offset, ULONGEST len,
|
|
ULONGEST *xfered_len) override;
|
|
int insert_breakpoint (struct gdbarch *,
|
|
struct bp_target_info *) override;
|
|
int remove_breakpoint (struct gdbarch *,
|
|
struct bp_target_info *,
|
|
enum remove_bp_reason) override;
|
|
|
|
bool has_execution (inferior *inf) override;
|
|
};
|
|
|
|
static record_full_target record_full_ops;
|
|
static record_full_core_target record_full_core_ops;
|
|
|
|
void
|
|
record_full_target::detach (inferior *inf, int from_tty)
|
|
{
|
|
record_detach (this, inf, from_tty);
|
|
}
|
|
|
|
void
|
|
record_full_target::disconnect (const char *args, int from_tty)
|
|
{
|
|
record_disconnect (this, args, from_tty);
|
|
}
|
|
|
|
void
|
|
record_full_core_target::disconnect (const char *args, int from_tty)
|
|
{
|
|
record_disconnect (this, args, from_tty);
|
|
}
|
|
|
|
void
|
|
record_full_target::mourn_inferior ()
|
|
{
|
|
record_mourn_inferior (this);
|
|
}
|
|
|
|
void
|
|
record_full_target::kill ()
|
|
{
|
|
record_kill (this);
|
|
}
|
|
|
|
/* Reset the incomplete instruction. */
|
|
|
|
static void
|
|
record_full_reset_incomplete ()
|
|
{
|
|
record_full_incomplete_instruction.effects.clear ();
|
|
record_full_incomplete_instruction.sigval.reset ();
|
|
record_full_incomplete_instruction.insn_num = 0;
|
|
}
|
|
|
|
/* See record-full.h. */
|
|
|
|
int
|
|
record_full_is_used (void)
|
|
{
|
|
struct target_ops *t;
|
|
|
|
t = find_record_target ();
|
|
return (t == &record_full_ops
|
|
|| t == &record_full_core_ops);
|
|
}
|
|
|
|
/* see record-full.h. */
|
|
bool
|
|
record_full_is_replaying ()
|
|
{
|
|
auto target = dynamic_cast<record_full_target *>
|
|
(current_inferior ()->target_at (record_stratum));
|
|
return target != nullptr && RECORD_FULL_IS_REPLAY;
|
|
}
|
|
|
|
|
|
/* Command lists for "set/show record full". */
|
|
static struct cmd_list_element *set_record_full_cmdlist;
|
|
static struct cmd_list_element *show_record_full_cmdlist;
|
|
|
|
/* Command list for "record full". */
|
|
static struct cmd_list_element *record_full_cmdlist;
|
|
|
|
static void record_full_goto_insn (size_t target_insn,
|
|
enum exec_direction_kind dir);
|
|
|
|
static void
|
|
record_full_reset_history ()
|
|
{
|
|
record_full_insn_count = 0;
|
|
record_full_next_insn = 0;
|
|
|
|
record_full_log.clear ();
|
|
}
|
|
|
|
/* Release all record entries after the INDEXth entry in the log. */
|
|
|
|
static void
|
|
record_full_log_release_following (int index)
|
|
{
|
|
if (record_full_log.empty ())
|
|
return;
|
|
|
|
for (int i = record_full_log.size () - 1; i > index; i--)
|
|
record_full_log.pop_back ();
|
|
/* Set the next instruction to be past the end of the log so we
|
|
start recording if the user moves forward again. */
|
|
record_full_next_insn = index;
|
|
}
|
|
|
|
/* Save the incomplete instruction in the log. */
|
|
|
|
static void
|
|
record_full_save_instruction ()
|
|
{
|
|
++record_full_insn_count;
|
|
record_full_incomplete_instruction.insn_num = record_full_insn_count;
|
|
record_full_incomplete_instruction.effects.shrink_to_fit ();
|
|
record_full_log.push_back (std::move (record_full_incomplete_instruction));
|
|
record_full_next_insn++;
|
|
|
|
record_full_reset_incomplete ();
|
|
}
|
|
|
|
/* Delete the first instruction from the beginning of the log, to make
|
|
room for adding a new instruction at the end of the log. */
|
|
|
|
static void
|
|
record_full_log_release_first (void)
|
|
{
|
|
if (record_full_log.empty ())
|
|
return;
|
|
|
|
record_full_log.pop_front ();
|
|
--record_full_next_insn;
|
|
}
|
|
|
|
/* Add a struct record_full_entry to record_full_arch_list. */
|
|
|
|
static void
|
|
record_full_arch_list_add (record_full_entry &&rec)
|
|
{
|
|
record_full_incomplete_instruction.effects.push_back (std::move (rec));
|
|
}
|
|
|
|
/* Record the value of a register NUM to record_full_arch_list. */
|
|
|
|
int
|
|
record_full_arch_list_add_reg (struct regcache *regcache, int regnum)
|
|
{
|
|
record_full_entry rec (record_full_reg, regcache->arch (), regnum);
|
|
|
|
if (record_debug > 1)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: add register num = %d to "
|
|
"record list.\n",
|
|
regnum);
|
|
|
|
regcache->cooked_read (regnum, rec.get_loc ());
|
|
|
|
if (regnum == gdbarch_pc_regnum (regcache->arch ()))
|
|
record_full_incomplete_instruction.pc = std::move (rec.reg ());
|
|
else
|
|
record_full_arch_list_add (std::move (rec));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Record the value of a region of memory whose address is ADDR and
|
|
length is LEN to record_full_arch_list. */
|
|
|
|
int
|
|
record_full_arch_list_add_mem (CORE_ADDR addr, int len)
|
|
{
|
|
record_full_entry rec (record_full_mem, addr, len);
|
|
|
|
if (record_debug > 1)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: add mem addr = %s len = %d to "
|
|
"record list.\n",
|
|
paddress (current_inferior ()->arch (), addr), len);
|
|
|
|
if (!addr) /* FIXME: Why? Some arch must permit it... */
|
|
return 0;
|
|
|
|
if (record_read_memory (current_inferior ()->arch (), addr,
|
|
rec.get_loc (), len))
|
|
return -1;
|
|
|
|
record_full_arch_list_add (std::move (rec));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
record_full_check_insn_num (void)
|
|
{
|
|
if (record_full_log.size () == record_full_insn_max_num)
|
|
{
|
|
/* Ask user what to do. */
|
|
if (record_full_stop_at_limit)
|
|
{
|
|
if (!yquery (_("Do you want to auto delete previous execution "
|
|
"log entries when record/replay buffer becomes "
|
|
"full (record full stop-at-limit)?")))
|
|
error (_("Process record: stopped by user."));
|
|
record_full_stop_at_limit = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Before inferior step (when GDB record the running message, inferior
|
|
only can step), GDB will call this function to record the values to
|
|
record_full_log. This function will call gdbarch_process_record to
|
|
record the running message of inferior and set them to
|
|
record_full_arch_list, and add it to record_full_log. */
|
|
|
|
static void
|
|
record_full_message (struct regcache *regcache, enum gdb_signal signal)
|
|
{
|
|
int ret;
|
|
struct gdbarch *gdbarch = regcache->arch ();
|
|
|
|
try
|
|
{
|
|
record_full_reset_incomplete ();
|
|
|
|
/* Check record_full_insn_num. */
|
|
record_full_check_insn_num ();
|
|
|
|
/* 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 they do during the replay
|
|
mode will change the behavior of the child. If they try,
|
|
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_full_wait, not
|
|
record_full_resume. */
|
|
if (signal != GDB_SIGNAL_0 && !record_full_log.empty ())
|
|
record_full_log[record_full_next_insn - 1].sigval = signal;
|
|
|
|
if (signal == GDB_SIGNAL_0
|
|
|| !gdbarch_process_record_signal_p (gdbarch))
|
|
ret = gdbarch_process_record (gdbarch,
|
|
regcache,
|
|
regcache_read_pc (regcache));
|
|
else
|
|
ret = gdbarch_process_record_signal (gdbarch,
|
|
regcache,
|
|
signal);
|
|
|
|
if (ret > 0)
|
|
error (_("Process record: inferior program stopped."));
|
|
if (ret < 0)
|
|
error (_("Process record: failed to record execution log."));
|
|
}
|
|
catch (const gdb_exception &ex)
|
|
{
|
|
record_full_reset_incomplete ();
|
|
throw;
|
|
}
|
|
|
|
record_full_save_instruction ();
|
|
|
|
if (record_full_log.size () == record_full_insn_max_num)
|
|
record_full_log_release_first ();
|
|
}
|
|
|
|
static bool
|
|
record_full_message_wrapper_safe (struct regcache *regcache,
|
|
enum gdb_signal signal)
|
|
{
|
|
try
|
|
{
|
|
record_full_message (regcache, signal);
|
|
}
|
|
catch (const gdb_exception_error &ex)
|
|
{
|
|
exception_print (gdb_stderr, ex);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Set to 1 if record_full_store_registers and record_full_xfer_partial
|
|
doesn't need record. */
|
|
|
|
static int record_full_gdb_operation_disable = 0;
|
|
|
|
scoped_restore_tmpl<int>
|
|
record_full_gdb_operation_disable_set (void)
|
|
{
|
|
return make_scoped_restore (&record_full_gdb_operation_disable, 1);
|
|
}
|
|
|
|
/* Flag set to TRUE for target_stopped_by_watchpoint. */
|
|
static enum target_stop_reason record_full_stop_reason
|
|
= TARGET_STOPPED_BY_NO_REASON;
|
|
|
|
/* Execute one entry in the log by executing all the effects. */
|
|
|
|
void record_full_instruction::exec_insn (regcache *regcache)
|
|
{
|
|
pc.execute (regcache);
|
|
for (auto &entry : effects)
|
|
if (entry.execute (regcache))
|
|
record_full_stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
|
|
}
|
|
|
|
static void record_full_restore (struct bfd &cbfd);
|
|
|
|
/* Asynchronous signal handle registered as event loop source for when
|
|
we have pending events ready to be passed to the core. */
|
|
|
|
static struct async_event_handler *record_full_async_inferior_event_token;
|
|
|
|
static void
|
|
record_full_async_inferior_event_handler (gdb_client_data data)
|
|
{
|
|
inferior_event_handler (INF_REG_EVENT);
|
|
}
|
|
|
|
/* Open the process record target for 'core' files. CBFD is the core file
|
|
containing the record information. */
|
|
|
|
static void
|
|
record_full_core_open_1 (struct bfd &cbfd)
|
|
{
|
|
regcache *regcache = get_thread_regcache (inferior_thread ());
|
|
int regnum = gdbarch_num_regs (regcache->arch ());
|
|
int i;
|
|
|
|
/* Get record_full_core_regbuf. */
|
|
target_fetch_registers (regcache, -1);
|
|
record_full_core_regbuf = new detached_regcache (regcache->arch (), false);
|
|
|
|
for (i = 0; i < regnum; i ++)
|
|
record_full_core_regbuf->raw_supply (i, *regcache);
|
|
|
|
record_full_core_sections = build_section_table (&cbfd);
|
|
|
|
current_inferior ()->push_target (&record_full_core_ops);
|
|
record_full_restore (cbfd);
|
|
}
|
|
|
|
/* Open the process record target for 'live' processes. */
|
|
|
|
static void
|
|
record_full_open_1 ()
|
|
{
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "Process record: record_full_open_1\n");
|
|
|
|
/* check exec */
|
|
if (!target_has_execution ())
|
|
error (_("Process record: the program is not being run."));
|
|
if (non_stop)
|
|
error (_("Process record target can't debug inferior in non-stop mode "
|
|
"(non-stop)."));
|
|
|
|
if (!gdbarch_process_record_p (current_inferior ()->arch ()))
|
|
error (_("Process record: the current architecture doesn't support "
|
|
"record function."));
|
|
|
|
current_inferior ()->push_target (&record_full_ops);
|
|
}
|
|
|
|
static void record_full_init_record_breakpoints (void);
|
|
|
|
/* Open the process record target. */
|
|
|
|
static void
|
|
record_full_open (const char *args, int from_tty)
|
|
{
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "Process record: record_full_open\n");
|
|
|
|
if (args != nullptr)
|
|
error (_("Trailing junk: '%s'"), args);
|
|
|
|
record_preopen ();
|
|
|
|
/* Reset */
|
|
record_full_reset_history ();
|
|
|
|
bfd *cbfd = get_inferior_core_bfd (current_inferior ());
|
|
if (cbfd != nullptr)
|
|
record_full_core_open_1 (*cbfd);
|
|
else
|
|
record_full_open_1 ();
|
|
|
|
/* Register extra event sources in the event loop. */
|
|
record_full_async_inferior_event_token
|
|
= create_async_event_handler (record_full_async_inferior_event_handler,
|
|
NULL, "record-full");
|
|
|
|
record_full_init_record_breakpoints ();
|
|
|
|
interps_notify_record_changed (current_inferior (), 1, "full", NULL);
|
|
}
|
|
|
|
/* "close" target method. Close the process record target. */
|
|
|
|
void
|
|
record_full_base_target::close ()
|
|
{
|
|
struct record_full_core_buf_entry *entry;
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "Process record: record_full_close\n");
|
|
|
|
record_full_reset_history ();
|
|
|
|
/* Release record_full_core_regbuf. */
|
|
if (record_full_core_regbuf)
|
|
{
|
|
delete record_full_core_regbuf;
|
|
record_full_core_regbuf = NULL;
|
|
}
|
|
|
|
/* Release record_full_core_buf_list. */
|
|
while (record_full_core_buf_list)
|
|
{
|
|
entry = record_full_core_buf_list;
|
|
record_full_core_buf_list = record_full_core_buf_list->prev;
|
|
xfree (entry);
|
|
}
|
|
|
|
if (record_full_async_inferior_event_token)
|
|
delete_async_event_handler (&record_full_async_inferior_event_token);
|
|
}
|
|
|
|
/* "async" target method. */
|
|
|
|
void
|
|
record_full_base_target::async (bool enable)
|
|
{
|
|
if (enable)
|
|
mark_async_event_handler (record_full_async_inferior_event_token);
|
|
else
|
|
clear_async_event_handler (record_full_async_inferior_event_token);
|
|
|
|
beneath ()->async (enable);
|
|
}
|
|
|
|
/* The PTID and STEP arguments last passed to
|
|
record_full_target::resume. */
|
|
static ptid_t record_full_resume_ptid = null_ptid;
|
|
static int record_full_resume_step = 0;
|
|
|
|
/* True if we've been resumed, and so each record_full_wait call should
|
|
advance execution. If this is false, record_full_wait will return a
|
|
TARGET_WAITKIND_IGNORE. */
|
|
static int record_full_resumed = 0;
|
|
|
|
/* The execution direction of the last resume we got. This is
|
|
necessary for async mode. Vis (order is not strictly accurate):
|
|
|
|
1. user has the global execution direction set to forward
|
|
2. user does a reverse-step command
|
|
3. record_full_resume is called with global execution direction
|
|
temporarily switched to reverse
|
|
4. GDB's execution direction is reverted back to forward
|
|
5. target record notifies event loop there's an event to handle
|
|
6. infrun asks the target which direction was it going, and switches
|
|
the global execution direction accordingly (to reverse)
|
|
7. infrun polls an event out of the record target, and handles it
|
|
8. GDB goes back to the event loop, and goto #4.
|
|
*/
|
|
static enum exec_direction_kind record_full_execution_dir = EXEC_FORWARD;
|
|
|
|
/* "resume" target method. Resume the process record target. */
|
|
|
|
void
|
|
record_full_target::resume (ptid_t ptid, int step, enum gdb_signal signal)
|
|
{
|
|
record_full_resume_ptid = inferior_ptid;
|
|
record_full_resume_step = step;
|
|
record_full_resumed = 1;
|
|
record_full_execution_dir = ::execution_direction;
|
|
|
|
if (!RECORD_FULL_IS_REPLAY)
|
|
{
|
|
struct regcache *regcache = get_thread_regcache (inferior_thread ());
|
|
struct gdbarch *gdbarch = regcache->arch ();
|
|
|
|
record_full_message (regcache, signal);
|
|
|
|
if (!step)
|
|
{
|
|
/* This is not hard single step. */
|
|
if (!gdbarch_get_next_pcs_p (gdbarch))
|
|
{
|
|
/* This is a normal continue. */
|
|
step = 1;
|
|
}
|
|
else
|
|
{
|
|
/* This arch supports soft single step. */
|
|
if (thread_has_single_step_breakpoints_set (inferior_thread ()))
|
|
{
|
|
/* This is a soft single step. */
|
|
record_full_resume_step = 1;
|
|
}
|
|
else
|
|
step = !insert_single_step_breakpoints (gdbarch);
|
|
}
|
|
}
|
|
|
|
/* Make sure the target beneath reports all signals. */
|
|
target_pass_signals ({});
|
|
|
|
/* Disable range-stepping, forcing the process target to report stops for
|
|
all executed instructions, so we can record them all. */
|
|
process_stratum_target *proc_target
|
|
= current_inferior ()->process_target ();
|
|
for (thread_info &thread : all_non_exited_threads (proc_target, ptid))
|
|
thread.control.may_range_step = 0;
|
|
|
|
this->beneath ()->resume (ptid, step, signal);
|
|
}
|
|
}
|
|
|
|
static int record_full_get_sig = 0;
|
|
|
|
/* SIGINT signal handler, registered by "wait" method. */
|
|
|
|
static void
|
|
record_full_sig_handler (int signo)
|
|
{
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "Process record: get a signal\n");
|
|
|
|
/* It will break the running inferior in replay mode. */
|
|
record_full_resume_step = 1;
|
|
|
|
/* It will let record_full_wait set inferior status to get the signal
|
|
SIGINT. */
|
|
record_full_get_sig = 1;
|
|
}
|
|
|
|
/* "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 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 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_full_wait_1 (struct target_ops *ops,
|
|
ptid_t ptid, struct target_waitstatus *status,
|
|
target_wait_flags options)
|
|
{
|
|
scoped_restore restore_operation_disable
|
|
= record_full_gdb_operation_disable_set ();
|
|
scoped_signal_handler<SIGINT> interrupt_handler (record_full_sig_handler);
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: record_full_wait "
|
|
"record_full_resume_step = %d, "
|
|
"record_full_resumed = %d, direction=%s\n",
|
|
record_full_resume_step, record_full_resumed,
|
|
record_full_execution_dir == EXEC_FORWARD
|
|
? "forward" : "reverse");
|
|
|
|
if (!record_full_resumed)
|
|
{
|
|
gdb_assert ((options & TARGET_WNOHANG) != 0);
|
|
|
|
/* No interesting event. */
|
|
status->set_ignore ();
|
|
return minus_one_ptid;
|
|
}
|
|
|
|
record_full_get_sig = 0;
|
|
|
|
record_full_stop_reason = TARGET_STOPPED_BY_NO_REASON;
|
|
|
|
if (!RECORD_FULL_IS_REPLAY && ops != &record_full_core_ops)
|
|
{
|
|
if (record_full_resume_step)
|
|
{
|
|
/* This is a single step. */
|
|
return ops->beneath ()->wait (ptid, status, options);
|
|
}
|
|
else
|
|
{
|
|
/* This is not a single step. */
|
|
ptid_t ret;
|
|
CORE_ADDR tmp_pc;
|
|
struct gdbarch *gdbarch
|
|
= target_thread_architecture (record_full_resume_ptid);
|
|
|
|
while (1)
|
|
{
|
|
ret = ops->beneath ()->wait (ptid, status, options);
|
|
if (status->kind () == TARGET_WAITKIND_IGNORE)
|
|
{
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: record_full_wait "
|
|
"target beneath not done yet\n");
|
|
return ret;
|
|
}
|
|
|
|
for (thread_info &tp : all_non_exited_threads ())
|
|
delete_single_step_breakpoints (&tp);
|
|
|
|
if (record_full_resume_step)
|
|
return ret;
|
|
|
|
/* Is this a SIGTRAP? */
|
|
if (status->kind () == TARGET_WAITKIND_STOPPED
|
|
&& status->sig () == GDB_SIGNAL_TRAP)
|
|
{
|
|
struct regcache *regcache;
|
|
enum target_stop_reason *stop_reason_p
|
|
= &record_full_stop_reason;
|
|
|
|
/* 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 ();
|
|
switch_to_thread (current_inferior ()->process_target (),
|
|
ret);
|
|
regcache = get_thread_regcache (inferior_thread ());
|
|
tmp_pc = regcache_read_pc (regcache);
|
|
const address_space *aspace
|
|
= current_inferior ()->aspace.get ();
|
|
|
|
if (target_stopped_by_watchpoint ())
|
|
{
|
|
/* Always interested in watchpoints. */
|
|
}
|
|
else if (record_check_stopped_by_breakpoint (aspace, tmp_pc,
|
|
stop_reason_p))
|
|
{
|
|
/* There is a breakpoint here. Let the core
|
|
handle it. */
|
|
}
|
|
else
|
|
{
|
|
/* This is a single-step trap. Record the
|
|
insn and issue another step.
|
|
FIXME: this part can be a random SIGTRAP too.
|
|
But GDB cannot handle it. */
|
|
int step = 1;
|
|
|
|
if (!record_full_message_wrapper_safe (regcache,
|
|
GDB_SIGNAL_0))
|
|
{
|
|
status->set_stopped (GDB_SIGNAL_0);
|
|
break;
|
|
}
|
|
|
|
process_stratum_target *proc_target
|
|
= current_inferior ()->process_target ();
|
|
|
|
if (gdbarch_get_next_pcs_p (gdbarch))
|
|
{
|
|
/* Try to insert the software single step breakpoint.
|
|
If insert success, set step to 0. */
|
|
set_internal_state (proc_target, inferior_ptid,
|
|
THREAD_INT_STOPPED);
|
|
SCOPE_EXIT
|
|
{
|
|
set_internal_state (proc_target, inferior_ptid,
|
|
THREAD_INT_RUNNING);
|
|
};
|
|
|
|
reinit_frame_cache ();
|
|
step = !insert_single_step_breakpoints (gdbarch);
|
|
}
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: record_full_wait "
|
|
"issuing one more step in the "
|
|
"target beneath\n");
|
|
ops->beneath ()->resume (ptid, step, GDB_SIGNAL_0);
|
|
proc_target->commit_resumed_state = true;
|
|
proc_target->commit_resumed ();
|
|
proc_target->commit_resumed_state = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* The inferior is broken by a breakpoint or a signal. */
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch_to_thread (current_inferior ()->process_target (),
|
|
record_full_resume_ptid);
|
|
regcache *regcache = get_thread_regcache (inferior_thread ());
|
|
struct gdbarch *gdbarch = regcache->arch ();
|
|
const address_space *aspace = current_inferior ()->aspace.get ();
|
|
int continue_flag = 1;
|
|
|
|
try
|
|
{
|
|
CORE_ADDR tmp_pc;
|
|
|
|
record_full_stop_reason = TARGET_STOPPED_BY_NO_REASON;
|
|
status->set_stopped (GDB_SIGNAL_0);
|
|
|
|
if (execution_direction == EXEC_FORWARD)
|
|
{
|
|
tmp_pc = regcache_read_pc (regcache);
|
|
if (record_check_stopped_by_breakpoint (aspace, tmp_pc,
|
|
&record_full_stop_reason))
|
|
{
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: break at %s.\n",
|
|
paddress (gdbarch, tmp_pc));
|
|
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_full_next_insn is the next
|
|
instruction to be executed. */
|
|
if (execution_direction == EXEC_REVERSE)
|
|
record_full_next_insn--;
|
|
|
|
/* Loop over the record_full_log, looking for the next place to
|
|
stop. */
|
|
do
|
|
{
|
|
/* Check for beginning and end of log. */
|
|
if (execution_direction == EXEC_REVERSE
|
|
&& record_full_next_insn < 0)
|
|
{
|
|
/* Hit beginning of record log in reverse. */
|
|
status->set_no_history ();
|
|
record_full_next_insn = 0;
|
|
break;
|
|
}
|
|
if (execution_direction != EXEC_REVERSE
|
|
&& record_full_next_insn == record_full_log.size ())
|
|
{
|
|
/* Hit end of record log going forward. */
|
|
status->set_no_history ();
|
|
break;
|
|
}
|
|
|
|
record_full_log[record_full_next_insn].exec_insn (regcache);
|
|
|
|
/* step */
|
|
if (record_full_resume_step)
|
|
{
|
|
if (record_debug > 1)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: step.\n");
|
|
continue_flag = 0;
|
|
}
|
|
|
|
/* check breakpoint */
|
|
tmp_pc = regcache_read_pc (regcache);
|
|
if (record_check_stopped_by_breakpoint
|
|
(aspace, tmp_pc, &record_full_stop_reason))
|
|
{
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: break "
|
|
"at %s.\n",
|
|
paddress (gdbarch, tmp_pc));
|
|
|
|
continue_flag = 0;
|
|
}
|
|
|
|
if (record_full_stop_reason
|
|
== TARGET_STOPPED_BY_WATCHPOINT)
|
|
{
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: hit hw "
|
|
"watchpoint.\n");
|
|
continue_flag = 0;
|
|
}
|
|
if (record_full_log[record_full_next_insn].sigval.has_value ())
|
|
continue_flag = 0;
|
|
|
|
if (execution_direction == EXEC_REVERSE)
|
|
record_full_next_insn--;
|
|
else
|
|
record_full_next_insn++;
|
|
}
|
|
while (continue_flag);
|
|
|
|
if (record_full_next_insn < 0)
|
|
{
|
|
gdb_assert (execution_direction == EXEC_REVERSE);
|
|
record_full_next_insn = 0;
|
|
}
|
|
else if (record_full_next_insn > record_full_log.size ())
|
|
{
|
|
gdb_assert (execution_direction == EXEC_FORWARD);
|
|
record_full_next_insn = record_full_log.size ();
|
|
}
|
|
/* Reset the current instruction to point to the one to be replayed
|
|
moving forward. */
|
|
else if (execution_direction == EXEC_REVERSE)
|
|
record_full_next_insn++;
|
|
|
|
replay_out:
|
|
if (status->kind () == TARGET_WAITKIND_STOPPED)
|
|
{
|
|
int insn = (execution_direction == EXEC_FORWARD)
|
|
? record_full_next_insn - 1 : record_full_next_insn;
|
|
if (record_full_get_sig)
|
|
status->set_stopped (GDB_SIGNAL_INT);
|
|
else if (record_full_log[insn].sigval.has_value ())
|
|
status->set_stopped
|
|
(record_full_log[insn].sigval.value ());
|
|
else
|
|
status->set_stopped (GDB_SIGNAL_TRAP);
|
|
}
|
|
}
|
|
catch (const gdb_exception &ex)
|
|
{
|
|
if (execution_direction == EXEC_REVERSE)
|
|
record_full_next_insn++;
|
|
else
|
|
record_full_next_insn--;
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
return inferior_ptid;
|
|
}
|
|
|
|
ptid_t
|
|
record_full_base_target::wait (ptid_t ptid, struct target_waitstatus *status,
|
|
target_wait_flags options)
|
|
{
|
|
ptid_t return_ptid;
|
|
|
|
clear_async_event_handler (record_full_async_inferior_event_token);
|
|
|
|
return_ptid = record_full_wait_1 (this, ptid, status, options);
|
|
if (status->kind () != TARGET_WAITKIND_IGNORE)
|
|
{
|
|
/* We're reporting a stop. Make sure any spurious
|
|
target_wait(WNOHANG) doesn't advance the target until the
|
|
core wants us resumed again. */
|
|
record_full_resumed = 0;
|
|
}
|
|
return return_ptid;
|
|
}
|
|
|
|
bool
|
|
record_full_base_target::stopped_by_watchpoint ()
|
|
{
|
|
if (RECORD_FULL_IS_REPLAY)
|
|
return record_full_stop_reason == TARGET_STOPPED_BY_WATCHPOINT;
|
|
else
|
|
return beneath ()->stopped_by_watchpoint ();
|
|
}
|
|
|
|
std::vector<CORE_ADDR>
|
|
record_full_base_target::stopped_data_addresses ()
|
|
{
|
|
if (RECORD_FULL_IS_REPLAY)
|
|
return {};
|
|
else
|
|
return this->beneath ()->stopped_data_addresses ();
|
|
}
|
|
|
|
/* The stopped_by_sw_breakpoint method of target record-full. */
|
|
|
|
bool
|
|
record_full_base_target::stopped_by_sw_breakpoint ()
|
|
{
|
|
return record_full_stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT;
|
|
}
|
|
|
|
/* The supports_stopped_by_sw_breakpoint method of target
|
|
record-full. */
|
|
|
|
bool
|
|
record_full_base_target::supports_stopped_by_sw_breakpoint ()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* The stopped_by_hw_breakpoint method of target record-full. */
|
|
|
|
bool
|
|
record_full_base_target::stopped_by_hw_breakpoint ()
|
|
{
|
|
return record_full_stop_reason == TARGET_STOPPED_BY_HW_BREAKPOINT;
|
|
}
|
|
|
|
/* The supports_stopped_by_sw_breakpoint method of target
|
|
record-full. */
|
|
|
|
bool
|
|
record_full_base_target::supports_stopped_by_hw_breakpoint ()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* Record registers change (by user or by GDB) to list as an instruction. */
|
|
|
|
static void
|
|
record_full_registers_change (struct regcache *regcache, int regnum)
|
|
{
|
|
/* Check record_full_insn_num. */
|
|
record_full_check_insn_num ();
|
|
|
|
record_full_reset_incomplete ();
|
|
|
|
if (regnum < 0)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < gdbarch_num_regs (regcache->arch ()); i++)
|
|
{
|
|
if (record_full_arch_list_add_reg (regcache, i))
|
|
{
|
|
record_full_reset_incomplete ();
|
|
error (_("Process record: failed to record execution log."));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (record_full_arch_list_add_reg (regcache, regnum))
|
|
{
|
|
record_full_reset_incomplete ();
|
|
error (_("Process record: failed to record execution log."));
|
|
}
|
|
}
|
|
record_full_save_instruction ();
|
|
|
|
if (record_full_log.size () == record_full_insn_max_num)
|
|
record_full_log_release_first ();
|
|
}
|
|
|
|
/* "store_registers" method for process record target. */
|
|
|
|
void
|
|
record_full_target::store_registers (struct regcache *regcache, int regno)
|
|
{
|
|
if (!record_full_gdb_operation_disable)
|
|
{
|
|
if (RECORD_FULL_IS_REPLAY)
|
|
{
|
|
int n;
|
|
|
|
/* Let user choose if they want 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 (regcache->arch (),
|
|
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 (regcache->arch ());
|
|
i++)
|
|
regcache->invalidate (i);
|
|
}
|
|
else
|
|
regcache->invalidate (regno);
|
|
|
|
error (_("Process record canceled the operation."));
|
|
}
|
|
|
|
/* Destroy the record from here forward. */
|
|
record_full_log_release_following (record_full_next_insn);
|
|
}
|
|
|
|
record_full_registers_change (regcache, regno);
|
|
}
|
|
this->beneath ()->store_registers (regcache, regno);
|
|
}
|
|
|
|
/* "xfer_partial" method. Behavior is conditional on
|
|
RECORD_FULL_IS_REPLAY.
|
|
In replay mode, we cannot write memory unless we are willing to
|
|
invalidate the record/replay log from this point forward. */
|
|
|
|
enum target_xfer_status
|
|
record_full_target::xfer_partial (enum target_object object,
|
|
const char *annex, gdb_byte *readbuf,
|
|
const gdb_byte *writebuf, ULONGEST offset,
|
|
ULONGEST len, ULONGEST *xfered_len)
|
|
{
|
|
if (!record_full_gdb_operation_disable
|
|
&& (object == TARGET_OBJECT_MEMORY
|
|
|| object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
|
|
{
|
|
if (RECORD_FULL_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 (current_inferior ()->arch (), offset)))
|
|
error (_("Process record canceled the operation."));
|
|
|
|
/* Destroy the record from here forward. */
|
|
record_full_log_release_following (record_full_next_insn);
|
|
}
|
|
|
|
/* Check record_full_insn_num */
|
|
record_full_check_insn_num ();
|
|
|
|
/* Record registers change to list as an instruction. */
|
|
record_full_reset_incomplete ();
|
|
if (record_full_arch_list_add_mem (offset, len))
|
|
{
|
|
record_full_reset_incomplete ();
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
"Process record: failed to record "
|
|
"execution log.");
|
|
return TARGET_XFER_E_IO;
|
|
}
|
|
record_full_save_instruction ();
|
|
|
|
if (record_full_log.size () == record_full_insn_max_num)
|
|
record_full_log_release_first ();
|
|
}
|
|
|
|
return this->beneath ()->xfer_partial (object, annex, readbuf, writebuf,
|
|
offset, len, xfered_len);
|
|
}
|
|
|
|
/* This structure represents a breakpoint inserted while the record
|
|
target is active. We use this to know when to install/remove
|
|
breakpoints in/from the target beneath. For example, a breakpoint
|
|
may be inserted while recording, but removed when not replaying nor
|
|
recording. In that case, the breakpoint had not been inserted on
|
|
the target beneath, so we should not try to remove it there. */
|
|
|
|
struct record_full_breakpoint
|
|
{
|
|
record_full_breakpoint (struct address_space *address_space_,
|
|
CORE_ADDR addr_,
|
|
bool in_target_beneath_)
|
|
: address_space (address_space_),
|
|
addr (addr_),
|
|
in_target_beneath (in_target_beneath_)
|
|
{
|
|
}
|
|
|
|
/* The address and address space the breakpoint was set at. */
|
|
struct address_space *address_space;
|
|
CORE_ADDR addr;
|
|
|
|
/* True when the breakpoint has been also installed in the target
|
|
beneath. This will be false for breakpoints set during replay or
|
|
when recording. */
|
|
bool in_target_beneath;
|
|
};
|
|
|
|
/* The list of breakpoints inserted while the record target is
|
|
active. */
|
|
static std::vector<record_full_breakpoint> record_full_breakpoints;
|
|
|
|
/* Sync existing breakpoints to record_full_breakpoints. */
|
|
|
|
static void
|
|
record_full_init_record_breakpoints (void)
|
|
{
|
|
record_full_breakpoints.clear ();
|
|
|
|
for (bp_location *loc : all_bp_locations ())
|
|
{
|
|
if (loc->loc_type != bp_loc_software_breakpoint)
|
|
continue;
|
|
|
|
if (loc->inserted)
|
|
record_full_breakpoints.emplace_back
|
|
(loc->target_info.placed_address_space,
|
|
loc->target_info.placed_address, 1);
|
|
}
|
|
}
|
|
|
|
/* Behavior is conditional on RECORD_FULL_IS_REPLAY. We will not actually
|
|
insert or remove breakpoints in the real target when replaying, nor
|
|
when recording. */
|
|
|
|
int
|
|
record_full_target::insert_breakpoint (struct gdbarch *gdbarch,
|
|
struct bp_target_info *bp_tgt)
|
|
{
|
|
bool in_target_beneath = false;
|
|
|
|
if (!RECORD_FULL_IS_REPLAY)
|
|
{
|
|
/* When recording, we currently always single-step, so we don't
|
|
really need to install regular breakpoints in the inferior.
|
|
However, we do have to insert software single-step
|
|
breakpoints, in case the target can't hardware step. To keep
|
|
things simple, we always insert. */
|
|
|
|
scoped_restore restore_operation_disable
|
|
= record_full_gdb_operation_disable_set ();
|
|
|
|
int ret = this->beneath ()->insert_breakpoint (gdbarch, bp_tgt);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
in_target_beneath = true;
|
|
}
|
|
|
|
/* Use the existing entries if found in order to avoid duplication
|
|
in record_full_breakpoints. */
|
|
|
|
for (const record_full_breakpoint &bp : record_full_breakpoints)
|
|
{
|
|
if (bp.addr == bp_tgt->placed_address
|
|
&& bp.address_space == bp_tgt->placed_address_space)
|
|
{
|
|
gdb_assert (bp.in_target_beneath == in_target_beneath);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
record_full_breakpoints.emplace_back (bp_tgt->placed_address_space,
|
|
bp_tgt->placed_address,
|
|
in_target_beneath);
|
|
return 0;
|
|
}
|
|
|
|
/* "remove_breakpoint" method for process record target. */
|
|
|
|
int
|
|
record_full_target::remove_breakpoint (struct gdbarch *gdbarch,
|
|
struct bp_target_info *bp_tgt,
|
|
enum remove_bp_reason reason)
|
|
{
|
|
for (auto iter = record_full_breakpoints.begin ();
|
|
iter != record_full_breakpoints.end ();
|
|
++iter)
|
|
{
|
|
struct record_full_breakpoint &bp = *iter;
|
|
|
|
if (bp.addr == bp_tgt->placed_address
|
|
&& bp.address_space == bp_tgt->placed_address_space)
|
|
{
|
|
if (bp.in_target_beneath)
|
|
{
|
|
scoped_restore restore_operation_disable
|
|
= record_full_gdb_operation_disable_set ();
|
|
|
|
int ret = this->beneath ()->remove_breakpoint (gdbarch, bp_tgt,
|
|
reason);
|
|
if (ret != 0)
|
|
return ret;
|
|
}
|
|
|
|
if (reason == REMOVE_BREAKPOINT)
|
|
unordered_remove (record_full_breakpoints, iter);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
gdb_assert_not_reached ("removing unknown breakpoint");
|
|
}
|
|
|
|
/* "can_execute_reverse" method for process record target. */
|
|
|
|
bool
|
|
record_full_base_target::can_execute_reverse ()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* "get_bookmark" method for process record and prec over core. */
|
|
|
|
gdb_byte *
|
|
record_full_base_target::get_bookmark (const char *args, int from_tty)
|
|
{
|
|
char *ret = NULL;
|
|
ULONGEST insn_num = 0;
|
|
|
|
if (record_full_log.empty ())
|
|
return (gdb_byte *) ret;
|
|
|
|
if (record_full_next_insn > 0)
|
|
insn_num = record_full_log[record_full_next_insn - 1].insn_num;
|
|
|
|
/* Return stringified form of instruction count. */
|
|
ret = xstrdup (pulongest (insn_num));
|
|
|
|
if (record_debug)
|
|
{
|
|
if (ret)
|
|
gdb_printf (gdb_stdlog,
|
|
"record_full_get_bookmark returns %s\n", ret);
|
|
else
|
|
gdb_printf (gdb_stdlog,
|
|
"record_full_get_bookmark returns NULL\n");
|
|
}
|
|
return (gdb_byte *) ret;
|
|
}
|
|
|
|
/* "goto_bookmark" method for process record and prec over core. */
|
|
|
|
void
|
|
record_full_base_target::goto_bookmark (const gdb_byte *raw_bookmark,
|
|
int from_tty)
|
|
{
|
|
const char *bookmark = (const char *) raw_bookmark;
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
"record_full_goto_bookmark receives %s\n", bookmark);
|
|
|
|
std::string name_holder;
|
|
if (bookmark[0] == '\'' || bookmark[0] == '\"')
|
|
{
|
|
if (bookmark[strlen (bookmark) - 1] != bookmark[0])
|
|
error (_("Unbalanced quotes: %s"), bookmark);
|
|
|
|
name_holder = std::string (bookmark + 1, strlen (bookmark) - 2);
|
|
bookmark = name_holder.c_str ();
|
|
}
|
|
|
|
record_goto (bookmark);
|
|
}
|
|
|
|
enum exec_direction_kind
|
|
record_full_base_target::execution_direction ()
|
|
{
|
|
return record_full_execution_dir;
|
|
}
|
|
|
|
/* The record_method method of target record-full. */
|
|
|
|
enum record_method
|
|
record_full_base_target::record_method (ptid_t ptid)
|
|
{
|
|
return RECORD_METHOD_FULL;
|
|
}
|
|
|
|
void
|
|
record_full_base_target::info_record ()
|
|
{
|
|
if (RECORD_FULL_IS_REPLAY)
|
|
gdb_printf (_("Replay mode:\n"));
|
|
else
|
|
gdb_printf (_("Record mode:\n"));
|
|
|
|
/* Do we have a log at all? */
|
|
if (!record_full_log.empty ())
|
|
{
|
|
/* Display instruction number for first instruction in the log. */
|
|
gdb_printf (_("Lowest recorded instruction number is %u.\n"),
|
|
record_full_log[0].insn_num);
|
|
|
|
/* If in replay mode, display where we are in the log. */
|
|
if (RECORD_FULL_IS_REPLAY)
|
|
gdb_printf (_("Current instruction number is %u.\n"),
|
|
record_full_log[record_full_next_insn].insn_num);
|
|
|
|
/* Display instruction number for last instruction in the log. */
|
|
gdb_printf (_("Highest recorded instruction number is %s.\n"),
|
|
pulongest (record_full_insn_count));
|
|
|
|
/* Display log count. */
|
|
gdb_printf (_("Log contains %lu instructions.\n"),
|
|
(unsigned long int) record_full_log.size ());
|
|
}
|
|
else
|
|
gdb_printf (_("No instructions have been logged.\n"));
|
|
|
|
/* Display max log size. */
|
|
gdb_printf (_("Max logged instructions is %u.\n"),
|
|
record_full_insn_max_num);
|
|
}
|
|
|
|
bool
|
|
record_full_base_target::supports_delete_record ()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* The "delete_record" target method. */
|
|
|
|
void
|
|
record_full_base_target::delete_record ()
|
|
{
|
|
record_full_reset_history ();
|
|
}
|
|
|
|
/* The "record_is_replaying" target method. */
|
|
|
|
bool
|
|
record_full_base_target::record_is_replaying (ptid_t ptid)
|
|
{
|
|
return RECORD_FULL_IS_REPLAY;
|
|
}
|
|
|
|
/* The "record_will_replay" target method. */
|
|
|
|
bool
|
|
record_full_base_target::record_will_replay (ptid_t ptid, int dir)
|
|
{
|
|
/* We can currently only record when executing forwards. Should we be able
|
|
to record when executing backwards on targets that support reverse
|
|
execution, this needs to be changed. */
|
|
|
|
return RECORD_FULL_IS_REPLAY || dir == EXEC_REVERSE;
|
|
}
|
|
|
|
/* Go to a specific entry. */
|
|
|
|
static void
|
|
record_full_goto_entry (size_t target_insn)
|
|
{
|
|
if (target_insn >= record_full_log.size ())
|
|
error (_("Target insn not found."));
|
|
else if (target_insn == record_full_next_insn)
|
|
error (_("Already at target insn."));
|
|
else if (target_insn > record_full_next_insn)
|
|
{
|
|
gdb_printf (_("Go forward to insn number %s\n"),
|
|
pulongest (record_full_log[target_insn].insn_num));
|
|
record_full_goto_insn (target_insn, EXEC_FORWARD);
|
|
}
|
|
else
|
|
{
|
|
gdb_printf (_("Go backward to insn number %s\n"),
|
|
pulongest (target_insn));
|
|
record_full_goto_insn (target_insn, EXEC_REVERSE);
|
|
}
|
|
|
|
registers_changed ();
|
|
reinit_frame_cache ();
|
|
|
|
thread_info *thr = inferior_thread ();
|
|
thr->set_stop_pc (regcache_read_pc (get_thread_regcache (thr)));
|
|
print_stack_frame (get_selected_frame (), 1, SRC_AND_LOC, 1);
|
|
}
|
|
|
|
/* The "goto_record_begin" target method. */
|
|
|
|
void
|
|
record_full_base_target::goto_record_begin ()
|
|
{
|
|
record_full_goto_entry (0);
|
|
}
|
|
|
|
/* The "goto_record_end" target method. */
|
|
|
|
void
|
|
record_full_base_target::goto_record_end ()
|
|
{
|
|
record_full_goto_entry (record_full_log.size () - 1);
|
|
}
|
|
|
|
/* The "goto_record" target method. */
|
|
|
|
void
|
|
record_full_base_target::goto_record (ULONGEST target_insn_num)
|
|
{
|
|
size_t target_insn;
|
|
for (target_insn = 0;
|
|
target_insn < record_full_log.size ();
|
|
target_insn ++)
|
|
if (record_full_log[target_insn].insn_num == target_insn_num)
|
|
break;
|
|
|
|
if (target_insn == record_full_log.size ())
|
|
error ("instruction number not available");
|
|
|
|
record_full_goto_entry (target_insn);
|
|
}
|
|
|
|
/* The "record_stop_replaying" target method. */
|
|
|
|
void
|
|
record_full_base_target::record_stop_replaying ()
|
|
{
|
|
if (RECORD_FULL_IS_REPLAY)
|
|
goto_record_end ();
|
|
}
|
|
|
|
/* "resume" method for prec over corefile. */
|
|
|
|
void
|
|
record_full_core_target::resume (ptid_t ptid, int step,
|
|
enum gdb_signal signal)
|
|
{
|
|
record_full_resume_step = step;
|
|
record_full_resume_ptid = ptid;
|
|
record_full_resumed = 1;
|
|
record_full_execution_dir = ::execution_direction;
|
|
}
|
|
|
|
/* "kill" method for prec over corefile. */
|
|
|
|
void
|
|
record_full_core_target::kill ()
|
|
{
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "Process record: record_full_core_kill\n");
|
|
|
|
current_inferior ()->unpush_target (this);
|
|
}
|
|
|
|
/* "fetch_registers" method for prec over corefile. */
|
|
|
|
void
|
|
record_full_core_target::fetch_registers (struct regcache *regcache,
|
|
int regno)
|
|
{
|
|
if (regno < 0)
|
|
{
|
|
int num = gdbarch_num_regs (regcache->arch ());
|
|
int i;
|
|
|
|
for (i = 0; i < num; i ++)
|
|
regcache->raw_supply (i, *record_full_core_regbuf);
|
|
}
|
|
else
|
|
regcache->raw_supply (regno, *record_full_core_regbuf);
|
|
}
|
|
|
|
/* "prepare_to_store" method for prec over corefile. */
|
|
|
|
void
|
|
record_full_core_target::prepare_to_store (struct regcache *regcache)
|
|
{
|
|
}
|
|
|
|
/* "store_registers" method for prec over corefile. */
|
|
|
|
void
|
|
record_full_core_target::store_registers (struct regcache *regcache,
|
|
int regno)
|
|
{
|
|
if (record_full_gdb_operation_disable)
|
|
record_full_core_regbuf->raw_supply (regno, *regcache);
|
|
else
|
|
error (_("You can't do that without a process to debug."));
|
|
}
|
|
|
|
/* "xfer_partial" method for prec over corefile. */
|
|
|
|
enum target_xfer_status
|
|
record_full_core_target::xfer_partial (enum target_object object,
|
|
const char *annex, gdb_byte *readbuf,
|
|
const gdb_byte *writebuf, ULONGEST offset,
|
|
ULONGEST len, ULONGEST *xfered_len)
|
|
{
|
|
if (object == TARGET_OBJECT_MEMORY)
|
|
{
|
|
if (record_full_gdb_operation_disable || !writebuf)
|
|
{
|
|
for (target_section &p : record_full_core_sections)
|
|
{
|
|
if (offset >= p.addr)
|
|
{
|
|
struct record_full_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);
|
|
|
|
*xfered_len = len;
|
|
return TARGET_XFER_OK;
|
|
}
|
|
/* Get record_full_core_buf_entry. */
|
|
for (entry = record_full_core_buf_list; entry;
|
|
entry = entry->prev)
|
|
if (entry->p == &p)
|
|
break;
|
|
if (writebuf)
|
|
{
|
|
if (!entry)
|
|
{
|
|
/* Add a new entry. */
|
|
entry = XNEW (struct record_full_core_buf_entry);
|
|
entry->p = &p;
|
|
if (!bfd_malloc_and_get_section
|
|
(p.the_bfd_section->owner,
|
|
p.the_bfd_section,
|
|
&entry->buf))
|
|
{
|
|
xfree (entry);
|
|
return TARGET_XFER_EOF;
|
|
}
|
|
entry->prev = record_full_core_buf_list;
|
|
record_full_core_buf_list = entry;
|
|
}
|
|
|
|
memcpy (entry->buf + sec_offset, writebuf,
|
|
(size_t) len);
|
|
}
|
|
else
|
|
{
|
|
if (!entry)
|
|
return this->beneath ()->xfer_partial (object, annex,
|
|
readbuf, writebuf,
|
|
offset, len,
|
|
xfered_len);
|
|
|
|
memcpy (readbuf, entry->buf + sec_offset,
|
|
(size_t) len);
|
|
}
|
|
|
|
*xfered_len = len;
|
|
return TARGET_XFER_OK;
|
|
}
|
|
}
|
|
|
|
return TARGET_XFER_E_IO;
|
|
}
|
|
else
|
|
error (_("You can't do that without a process to debug."));
|
|
}
|
|
|
|
return this->beneath ()->xfer_partial (object, annex,
|
|
readbuf, writebuf, offset, len,
|
|
xfered_len);
|
|
}
|
|
|
|
/* "insert_breakpoint" method for prec over corefile. */
|
|
|
|
int
|
|
record_full_core_target::insert_breakpoint (struct gdbarch *gdbarch,
|
|
struct bp_target_info *bp_tgt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* "remove_breakpoint" method for prec over corefile. */
|
|
|
|
int
|
|
record_full_core_target::remove_breakpoint (struct gdbarch *gdbarch,
|
|
struct bp_target_info *bp_tgt,
|
|
enum remove_bp_reason reason)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* "has_execution" method for prec over corefile. */
|
|
|
|
bool
|
|
record_full_core_target::has_execution (inferior *inf)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* 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_full_end:
|
|
1 byte: record type (record_full_end, see enum record_full_type).
|
|
record_full_reg:
|
|
1 byte: record type (record_full_reg, see enum record_full_type).
|
|
8 bytes: register id (network byte order).
|
|
MAX_REGISTER_SIZE bytes: register value.
|
|
record_full_mem:
|
|
1 byte: record type (record_full_mem, see enum record_full_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_full_end:
|
|
1 byte: record type (record_full_end, see enum record_full_type).
|
|
4 bytes: signal
|
|
4 bytes: instruction count
|
|
record_full_reg:
|
|
1 byte: record type (record_full_reg, see enum record_full_type).
|
|
4 bytes: register id (network byte order).
|
|
n bytes: register value (n == actual register size).
|
|
(eg. 4 bytes for x86 general registers).
|
|
record_full_mem:
|
|
1 byte: record type (record_full_mem, see enum record_full_type).
|
|
4 bytes: memory length (network byte order).
|
|
8 bytes: memory address (network byte order).
|
|
n bytes: memory value (n == memory length).
|
|
|
|
Version 3 (all numbers are in network order).
|
|
4 bytes: Magic number (0x20260415).
|
|
NOTE: be sure to change whenever this file format changes!
|
|
|
|
Records:
|
|
record_full_instruction:
|
|
1 byte: signal.
|
|
4 bytes: number of reg and mem entries for this instruction.
|
|
4 bytes: instruction sequence number.
|
|
4 bytes: PC register ID.
|
|
N bytes: PC address of instruction (N == size of PC).
|
|
Effects:
|
|
record_full_reg:
|
|
1 byte: record_type (record_full_reg, see enum record_full_type).
|
|
4 bytes: Register ID.
|
|
n bytes: register value (n == actual register size).
|
|
record_full_mem:
|
|
1 byte: record_type (record_full_mem, see enum record_full_type).
|
|
4 bytes: memory length.
|
|
8 bytes: memory address.
|
|
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 %ps ('%s')."),
|
|
len, styled_string (file_name_style.style (),
|
|
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;
|
|
}
|
|
|
|
record_full_reg_entry
|
|
record_full_reg_entry::from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
|
|
{
|
|
uint32_t regnum;
|
|
regcache *cache = get_thread_regcache (inferior_thread ());
|
|
|
|
/* Get register number to regnum. */
|
|
bfdcore_read (cbfd, osec, ®num, sizeof (regnum),
|
|
bfd_offset);
|
|
regnum = netorder32 (regnum);
|
|
|
|
record_full_reg_entry reg (cache->arch (), regnum);
|
|
|
|
/* Get val. */
|
|
bfdcore_read (cbfd, osec, reg.get_loc (),
|
|
reg.len, bfd_offset);
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
" Reading register %d (1 "
|
|
"plus %lu plus %d bytes)\n",
|
|
reg.num,
|
|
(unsigned long) sizeof (regnum),
|
|
reg.len);
|
|
return reg;
|
|
|
|
}
|
|
|
|
record_full_mem_entry
|
|
record_full_mem_entry::from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
|
|
{
|
|
uint32_t len;
|
|
uint64_t addr;
|
|
|
|
/* Get len. */
|
|
bfdcore_read (cbfd, osec, &len, sizeof (len), bfd_offset);
|
|
len = netorder32 (len);
|
|
|
|
/* Get addr. */
|
|
bfdcore_read (cbfd, osec, &addr, sizeof (addr),
|
|
bfd_offset);
|
|
addr = netorder64 (addr);
|
|
|
|
record_full_mem_entry mem (addr, len);
|
|
|
|
/* Get val. */
|
|
bfdcore_read (cbfd, osec, mem.get_loc (),
|
|
len, bfd_offset);
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
" Reading memory %s (1 plus "
|
|
"%lu plus %lu plus %d bytes)\n",
|
|
paddress (get_current_arch (),
|
|
mem.addr),
|
|
(unsigned long) sizeof (addr),
|
|
(unsigned long) sizeof (len),
|
|
len);
|
|
|
|
return mem;
|
|
}
|
|
|
|
void
|
|
record_full_entry::from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
|
|
{
|
|
uint8_t rectype;
|
|
record_full_entry rec;
|
|
|
|
bfdcore_read (cbfd, osec, &rectype, sizeof (rectype), bfd_offset);
|
|
|
|
switch (rectype)
|
|
{
|
|
case record_full_reg: /* reg */
|
|
{
|
|
rec.entry = record_full_reg_entry::from_bfd (cbfd, osec, bfd_offset);
|
|
break;
|
|
}
|
|
|
|
case record_full_mem: /* mem */
|
|
{
|
|
rec.entry = record_full_mem_entry::from_bfd (cbfd, osec, bfd_offset);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
error (_("Bad entry type in core file %ps."),
|
|
styled_string (file_name_style.style (),
|
|
bfd_get_filename (cbfd)));
|
|
break;
|
|
}
|
|
record_full_arch_list_add (std::move (rec));
|
|
}
|
|
|
|
void
|
|
record_full_instruction::from_bfd (bfd *cbfd, asection *osec, int *bfd_offset)
|
|
{
|
|
uint32_t eff_count = 0;
|
|
uint8_t sigval;
|
|
uint32_t insn_num;
|
|
|
|
/* First read the generic information for an instruction. */
|
|
bfdcore_read (cbfd, osec, &sigval, sizeof (uint8_t), bfd_offset);
|
|
bfdcore_read (cbfd, osec, &eff_count, sizeof (uint32_t),
|
|
bfd_offset);
|
|
bfdcore_read (cbfd, osec, &insn_num, sizeof (uint32_t),
|
|
bfd_offset);
|
|
|
|
record_full_incomplete_instruction.insn_num = netorder32 (insn_num);
|
|
if (sigval != GDB_SIGNAL_0)
|
|
record_full_incomplete_instruction.sigval = (gdb_signal) sigval;
|
|
|
|
record_full_incomplete_instruction.pc
|
|
= record_full_reg_entry::from_bfd (cbfd, osec, bfd_offset);
|
|
|
|
eff_count = netorder32 (eff_count);
|
|
|
|
/* This deals with all the side effects. */
|
|
while (eff_count > 0)
|
|
{
|
|
eff_count--;
|
|
|
|
record_full_entry::from_bfd (cbfd, osec, bfd_offset);
|
|
}
|
|
}
|
|
|
|
/* Restore the execution log from core file CBFD. */
|
|
|
|
static void
|
|
record_full_restore (struct bfd &cbfd)
|
|
{
|
|
uint32_t magic;
|
|
asection *osec;
|
|
uint32_t osec_size;
|
|
int bfd_offset = 0;
|
|
|
|
/* "record_full_restore" can only be called when record list is empty. */
|
|
gdb_assert (record_full_log.empty ());
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "Restoring recording from core file.\n");
|
|
|
|
/* Now need to find our special note section. */
|
|
osec = bfd_get_section_by_name (&cbfd, "null0");
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "Find precord section %s.\n",
|
|
osec ? "succeeded" : "failed");
|
|
if (osec == NULL)
|
|
return;
|
|
osec_size = bfd_section_size (osec);
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "%s", bfd_section_name (osec));
|
|
|
|
/* Check the magic code. */
|
|
bfdcore_read (&cbfd, osec, &magic, sizeof (magic), &bfd_offset);
|
|
if (magic != RECORD_FULL_FILE_MAGIC)
|
|
{
|
|
if (magic == RECORD_FULL_FILE_MAGIC_OLD)
|
|
error (_("This old recording format is no longer supported."));
|
|
else
|
|
error (_("Version mismatch or file format error in core file %ps."),
|
|
styled_string (file_name_style.style (),
|
|
bfd_get_filename (&cbfd)));
|
|
}
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
" Reading 4-byte magic cookie "
|
|
"RECORD_FULL_FILE_MAGIC (0x%s)\n",
|
|
phex_nz (netorder32 (magic), 4));
|
|
|
|
try
|
|
{
|
|
while (bfd_offset < osec_size)
|
|
{
|
|
record_full_reset_incomplete ();
|
|
|
|
record_full_instruction::from_bfd (&cbfd, osec, &bfd_offset);
|
|
|
|
record_full_save_instruction ();
|
|
}
|
|
}
|
|
catch (const gdb_exception &ex)
|
|
{
|
|
record_full_reset_incomplete ();
|
|
throw;
|
|
}
|
|
|
|
/* Update record_full_insn_max_num. */
|
|
if (record_full_log.size () > record_full_insn_max_num)
|
|
{
|
|
record_full_insn_max_num = record_full_log.size ();
|
|
warning (_("Auto increase record/replay buffer limit to %u."),
|
|
record_full_insn_max_num);
|
|
}
|
|
|
|
/* When loading a recording, we'll always start at the oldest possible
|
|
instruction, no matter where the original recording was stopped. */
|
|
record_full_next_insn = 0;
|
|
|
|
/* Succeeded. */
|
|
gdb_printf (_("Restored records from core file %s.\n"),
|
|
bfd_get_filename (&cbfd));
|
|
|
|
print_stack_frame (get_selected_frame (), 1, SRC_AND_LOC, 1);
|
|
}
|
|
|
|
/* 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 %ps ('%s')."),
|
|
len, styled_string (file_name_style.style (),
|
|
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_full_restore (const char *args, int from_tty)
|
|
{
|
|
core_file_command (args, from_tty);
|
|
record_full_open (nullptr, from_tty);
|
|
}
|
|
|
|
void
|
|
record_full_reg_entry::to_bfd (const gdb_bfd_ref_ptr &obfd,
|
|
asection *osec, int *bfd_offset)
|
|
{
|
|
uint32_t regnum;
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
" Writing register %d (1 "
|
|
"plus %lu plus %d bytes)\n",
|
|
this->num,
|
|
(unsigned long) sizeof (regnum),
|
|
this->len);
|
|
|
|
/* Write regnum. */
|
|
regnum = netorder32 (this->num);
|
|
bfdcore_write (obfd.get (), osec, ®num, sizeof (regnum), bfd_offset);
|
|
|
|
/* Write regval. */
|
|
bfdcore_write (obfd.get (), osec, this->get_loc (), this->len, bfd_offset);
|
|
}
|
|
|
|
void
|
|
record_full_mem_entry::to_bfd (const gdb_bfd_ref_ptr &obfd,
|
|
asection *osec, int *bfd_offset,
|
|
gdbarch *gdbarch)
|
|
{
|
|
uint32_t len;
|
|
uint64_t addr;
|
|
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
" Writing memory %s (1 plus "
|
|
"%lu plus %lu plus %d bytes)\n",
|
|
paddress (gdbarch, this->addr),
|
|
(unsigned long) sizeof (addr),
|
|
(unsigned long) sizeof (len),
|
|
this->len);
|
|
|
|
/* Write memlen. */
|
|
len = netorder32 (this->len);
|
|
bfdcore_write (obfd.get (), osec, &len, sizeof (len), bfd_offset);
|
|
|
|
/* Write memaddr. */
|
|
addr = netorder64 (this->addr);
|
|
bfdcore_write (obfd.get (), osec, &addr, sizeof (addr), bfd_offset);
|
|
|
|
/* Write memval. */
|
|
bfdcore_write (obfd.get (), osec, this->get_loc (), this->len, bfd_offset);
|
|
}
|
|
|
|
void
|
|
record_full_entry::to_bfd (const gdb_bfd_ref_ptr &obfd, asection *osec,
|
|
int *bfd_offset, gdbarch *gdbarch)
|
|
{
|
|
/* Save entry. */
|
|
uint8_t type;
|
|
|
|
type = this->type ();
|
|
bfdcore_write (obfd.get (), osec, &type, sizeof (type), bfd_offset);
|
|
|
|
switch (type)
|
|
{
|
|
case record_full_reg: /* reg */
|
|
reg ().to_bfd (obfd, osec, bfd_offset);
|
|
break;
|
|
|
|
case record_full_mem: /* mem */
|
|
mem ().to_bfd (obfd, osec, bfd_offset, gdbarch);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
record_full_instruction::to_bfd (gdb_bfd_ref_ptr &obfd, asection *osec,
|
|
int *bfd_offset, gdbarch *gdbarch)
|
|
{
|
|
uint32_t eff_count = (uint32_t) this->effects.size ();
|
|
uint32_t insn_num = this->insn_num;
|
|
uint8_t sigval = (this->sigval.has_value ())
|
|
? this->sigval.value ()
|
|
: GDB_SIGNAL_0;
|
|
|
|
/* Signal. */
|
|
bfdcore_write (obfd.get (), osec, &sigval, sizeof (sigval), bfd_offset);
|
|
eff_count = netorder32 (eff_count);
|
|
/* Number of effects. */
|
|
insn_num = netorder32 (insn_num);
|
|
bfdcore_write (obfd.get (), osec, &eff_count, sizeof (eff_count),
|
|
bfd_offset);
|
|
/* Instruction number. */
|
|
bfdcore_write (obfd.get (), osec, &insn_num, sizeof (uint32_t),
|
|
bfd_offset);
|
|
|
|
this->pc.to_bfd (obfd, osec, bfd_offset);
|
|
|
|
for (auto &entry : this->effects)
|
|
{
|
|
entry.to_bfd (obfd, osec, bfd_offset, gdbarch);
|
|
}
|
|
}
|
|
|
|
/* Save the execution log to a file. We use a modified elf corefile
|
|
format, with an extra section for our data. */
|
|
|
|
void
|
|
record_full_base_target::save_record (const char *recfilename)
|
|
{
|
|
uint32_t magic;
|
|
struct gdbarch *gdbarch;
|
|
int save_size = 0;
|
|
asection *osec = NULL;
|
|
int bfd_offset = 0;
|
|
|
|
/* Open the save file. */
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog, "Saving execution log to core file '%s'\n",
|
|
recfilename);
|
|
|
|
/* Open the output file. */
|
|
gdb_bfd_ref_ptr obfd (create_gcore_bfd (recfilename));
|
|
|
|
/* Arrange to remove the output file on failure. */
|
|
gdb::unlinker unlink_file (recfilename);
|
|
|
|
/* Get the values of regcache and gdbarch. */
|
|
regcache *regcache = get_thread_regcache (inferior_thread ());
|
|
gdbarch = regcache->arch ();
|
|
|
|
/* Disable the GDB operation record. */
|
|
scoped_restore restore_operation_disable
|
|
= record_full_gdb_operation_disable_set ();
|
|
|
|
/* Reverse execute to the begin of record list. */
|
|
for (int i = record_full_next_insn - 1; i >= 0; i--)
|
|
record_full_log[i].exec_insn (regcache);
|
|
|
|
/* Compute the size needed for the extra bfd section. */
|
|
save_size = 4; /* magic cookie */
|
|
for (int i = record_full_log.size () - 1; i >= 0; i--)
|
|
{
|
|
/* Number of effects of an instruction. */
|
|
save_size += sizeof (uint32_t) + sizeof (uint8_t) + sizeof (uint32_t);
|
|
save_size += 4 + record_full_log[i].pc.len;
|
|
for (auto &entry : record_full_log[i].effects)
|
|
switch (entry.type ())
|
|
{
|
|
case record_full_reg:
|
|
save_size += 1 + 4 + entry.reg ().len;
|
|
break;
|
|
case record_full_mem:
|
|
save_size += 1 + 4 + 8 + entry.mem ().len;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Make the new bfd section. */
|
|
osec = bfd_make_section_anyway_with_flags (obfd.get (), "precord",
|
|
SEC_HAS_CONTENTS
|
|
| SEC_READONLY);
|
|
if (osec == NULL)
|
|
error (_("Failed to create 'precord' section for corefile %ps: %s"),
|
|
styled_string (file_name_style.style (), recfilename),
|
|
bfd_errmsg (bfd_get_error ()));
|
|
bfd_set_section_size (osec, save_size);
|
|
bfd_set_section_vma (osec, 0);
|
|
bfd_set_section_alignment (osec, 0);
|
|
|
|
/* Save corefile state. */
|
|
write_gcore_file (obfd.get ());
|
|
|
|
/* Write out the record log. */
|
|
/* Write the magic code. */
|
|
magic = RECORD_FULL_FILE_MAGIC;
|
|
if (record_debug)
|
|
gdb_printf (gdb_stdlog,
|
|
" Writing 4-byte magic cookie "
|
|
"RECORD_FULL_FILE_MAGIC (0x%s)\n",
|
|
phex_nz (magic, 4));
|
|
bfdcore_write (obfd.get (), osec, &magic, sizeof (magic), &bfd_offset);
|
|
|
|
/* Save the entries to recfd and forward execute to the end of
|
|
record list. */
|
|
for (int i = 0; i < record_full_log.size (); i++)
|
|
{
|
|
record_full_log[i].to_bfd (obfd, osec, &bfd_offset, gdbarch);
|
|
/* Execute entry. */
|
|
if (i < record_full_next_insn)
|
|
record_full_log[i].exec_insn (regcache);
|
|
}
|
|
|
|
unlink_file.keep ();
|
|
|
|
/* Succeeded. */
|
|
gdb_printf (_("Saved core file %s with execution log.\n"),
|
|
recfilename);
|
|
}
|
|
|
|
/* record_full_goto_insn -- rewind the record log (forward or backward,
|
|
depending on DIR) to the entry in position TARGET_INSN in the history,
|
|
changing the program state correspondingly. */
|
|
|
|
static void
|
|
record_full_goto_insn (size_t target_insn,
|
|
enum exec_direction_kind dir)
|
|
{
|
|
scoped_restore restore_operation_disable
|
|
= record_full_gdb_operation_disable_set ();
|
|
regcache *regcache = get_thread_regcache (inferior_thread ());
|
|
|
|
/* Assume everything is valid: we will hit the entry,
|
|
and we will not hit the end of the recording. */
|
|
|
|
if (dir == EXEC_REVERSE)
|
|
for (int i = record_full_next_insn; i > target_insn; i--)
|
|
record_full_log[i - 1].exec_insn (regcache);
|
|
else
|
|
for (int i = record_full_next_insn; i < target_insn; i++)
|
|
record_full_log[i].exec_insn (regcache);
|
|
|
|
record_full_next_insn = target_insn;
|
|
}
|
|
|
|
/* Alias for "target record-full". */
|
|
|
|
static void
|
|
cmd_record_full_start (const char *args, int from_tty)
|
|
{
|
|
execute_command ("target record-full", from_tty);
|
|
}
|
|
|
|
static void
|
|
set_record_full_insn_max_num (const char *args, int from_tty,
|
|
struct cmd_list_element *c)
|
|
{
|
|
if (record_full_log.size () > record_full_insn_max_num)
|
|
{
|
|
while (record_full_log.size () > record_full_insn_max_num)
|
|
record_full_log_release_first ();
|
|
}
|
|
}
|
|
|
|
/* Implement the 'maintenance print record-instruction' command. */
|
|
|
|
static void
|
|
maintenance_print_record_instruction (const char *args, int from_tty)
|
|
{
|
|
if (record_full_log.empty ())
|
|
error (_("Not enough recorded history"));
|
|
|
|
int offset = record_full_next_insn - 1;
|
|
/* Reduce the offset by 1 if the record_full_next_insn is after the end
|
|
so that we show the last recorded instruction instead of crashing. */
|
|
if (offset == record_full_log.size ())
|
|
offset--;
|
|
if (args != nullptr)
|
|
{
|
|
offset += value_as_long (parse_and_eval (args));
|
|
if (offset >= record_full_log.size () || offset < 0)
|
|
error (_("Not enough recorded history"));
|
|
}
|
|
auto to_print = record_full_log.begin () + offset;
|
|
|
|
gdbarch *arch = current_inferior ()->arch ();
|
|
struct value_print_options opts;
|
|
get_user_print_options (&opts);
|
|
opts.raw = true;
|
|
|
|
for (auto &entry : to_print->effects)
|
|
{
|
|
switch (entry.type ())
|
|
{
|
|
case record_full_reg:
|
|
{
|
|
type *regtype = gdbarch_register_type (arch, entry.reg ().num);
|
|
value *val
|
|
= value_from_contents (regtype,
|
|
entry.get_loc ());
|
|
gdb_printf ("Register %s changed: ",
|
|
gdbarch_register_name (arch, entry.reg ().num));
|
|
value_print (val, gdb_stdout, &opts);
|
|
gdb_printf ("\n");
|
|
break;
|
|
}
|
|
case record_full_mem:
|
|
{
|
|
record_full_mem_entry& mem = entry.mem ();
|
|
gdb_byte *b = entry.get_loc ();
|
|
gdb_printf ("%d bytes of memory at address %s changed from:",
|
|
mem.len,
|
|
print_core_address (arch, mem.addr));
|
|
for (int i = 0; i < mem.len; i++)
|
|
gdb_printf (" %02x", b[i]);
|
|
gdb_printf ("\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
type *regtype = gdbarch_register_type (arch, to_print->pc.num);
|
|
value *val = value_from_contents (regtype, to_print->pc.get_loc ());
|
|
gdb_printf ("Register %s changed: ",
|
|
gdbarch_register_name (arch, to_print->pc.num));
|
|
value_print (val, gdb_stdout, &opts);
|
|
gdb_printf ("\n");
|
|
}
|
|
|
|
INIT_GDB_FILE (record_full)
|
|
{
|
|
struct cmd_list_element *c;
|
|
|
|
add_target (record_full_target_info, record_full_open);
|
|
add_deprecated_target_alias (record_full_target_info, "record");
|
|
add_target (record_full_core_target_info, record_full_open);
|
|
|
|
add_prefix_cmd ("full", class_obscure, cmd_record_full_start,
|
|
_("Start full execution recording."), &record_full_cmdlist,
|
|
0, &record_cmdlist);
|
|
|
|
cmd_list_element *record_full_restore_cmd
|
|
= add_cmd ("restore", class_obscure, cmd_record_full_restore,
|
|
_("Restore the execution log from a file.\n\
|
|
Argument is filename. File must be created with 'record save'."),
|
|
&record_full_cmdlist);
|
|
set_cmd_completer (record_full_restore_cmd, deprecated_filename_completer);
|
|
|
|
/* Deprecate the old version without "full" prefix. */
|
|
c = add_alias_cmd ("restore", record_full_restore_cmd, class_obscure, 1,
|
|
&record_cmdlist);
|
|
set_cmd_completer (c, deprecated_filename_completer);
|
|
deprecate_cmd (c, "record full restore");
|
|
|
|
add_setshow_prefix_cmd ("full", class_support,
|
|
_("Set record options."),
|
|
_("Show record options."),
|
|
&set_record_full_cmdlist,
|
|
&show_record_full_cmdlist,
|
|
&set_record_cmdlist,
|
|
&show_record_cmdlist);
|
|
|
|
/* Record instructions number limit command. */
|
|
set_show_commands set_record_full_stop_at_limit_cmds
|
|
= add_setshow_boolean_cmd ("stop-at-limit", no_class,
|
|
&record_full_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_full_cmdlist,
|
|
&show_record_full_cmdlist);
|
|
|
|
c = add_alias_cmd ("stop-at-limit",
|
|
set_record_full_stop_at_limit_cmds.set, no_class, 1,
|
|
&set_record_cmdlist);
|
|
deprecate_cmd (c, "set record full stop-at-limit");
|
|
|
|
c = add_alias_cmd ("stop-at-limit",
|
|
set_record_full_stop_at_limit_cmds.show, no_class, 1,
|
|
&show_record_cmdlist);
|
|
deprecate_cmd (c, "show record full stop-at-limit");
|
|
|
|
set_show_commands record_full_insn_number_max_cmds
|
|
= add_setshow_uinteger_cmd ("insn-number-max", no_class,
|
|
&record_full_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. A value of either \"unlimited\" or zero means no\n\
|
|
limit. Default is 200000."),
|
|
set_record_full_insn_max_num,
|
|
NULL, &set_record_full_cmdlist,
|
|
&show_record_full_cmdlist);
|
|
|
|
c = add_alias_cmd ("insn-number-max", record_full_insn_number_max_cmds.set,
|
|
no_class, 1, &set_record_cmdlist);
|
|
deprecate_cmd (c, "set record full insn-number-max");
|
|
|
|
c = add_alias_cmd ("insn-number-max", record_full_insn_number_max_cmds.show,
|
|
no_class, 1, &show_record_cmdlist);
|
|
deprecate_cmd (c, "show record full insn-number-max");
|
|
|
|
set_show_commands record_full_memory_query_cmds
|
|
= add_setshow_boolean_cmd ("memory-query", no_class,
|
|
&record_full_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_full_cmdlist,
|
|
&show_record_full_cmdlist);
|
|
|
|
c = add_alias_cmd ("memory-query", record_full_memory_query_cmds.set,
|
|
no_class, 1, &set_record_cmdlist);
|
|
deprecate_cmd (c, "set record full memory-query");
|
|
|
|
c = add_alias_cmd ("memory-query", record_full_memory_query_cmds.show,
|
|
no_class, 1,&show_record_cmdlist);
|
|
deprecate_cmd (c, "show record full memory-query");
|
|
|
|
add_cmd ("record-instruction", class_maintenance,
|
|
maintenance_print_record_instruction,
|
|
_("\
|
|
Print a recorded instruction.\n\
|
|
If no argument is provided, print the last instruction recorded.\n\
|
|
If a negative argument is given, prints how the nth previous\n\
|
|
instruction will be undone.\n\
|
|
If a positive argument is given, prints\n\
|
|
how the nth following instruction will be redone."), &maintenanceprintlist);
|
|
}
|