* [RFA] Submit process record and replay third time, 3/9
@ 2009-01-08 5:46 teawater
2009-01-13 3:06 ` teawater
2009-01-22 13:23 ` Pedro Alves
0 siblings, 2 replies; 38+ messages in thread
From: teawater @ 2009-01-08 5:46 UTC (permalink / raw)
To: gdb-patches
[-- Attachment #1: Type: text/plain, Size: 2236 bytes --]
This patch add the process record and replay target. This is the core
part of process record and replay.
2009-01-08 Hui Zhu <teawater@gmail.com>
Process record and replay target.
* Makefile.in (record.c): New file.
* record.c, record.h: New file.
2008-12-28 Michael Snyder <msnyder@vmware.com>
* Comments, spelling, white space clean-ups.
2008-12-26 Michael Snyder <msnyder@vmware.com>
* record.h: Don't export record_not_record.
* record.c (record_not_record): Rename to in_record_wait.
(record_not_record_set): Rename to in_record_wait_set.
(record_not_record_cleanup): Rename to in_record_wait_cleanup.
(record_store_registers): Check in_record_wait flag.
(record_xfer_partial): Ditto.
2008-10-07 Michael Snyder <msnyder@vmware.com>
* record.h (record_exec_direction): Delete.
(RECORD_IS_REPLAY): Consult infrun global direction variable.
* record.c: (record_wait_cleanups): Use infrun state variable.
(record_wait): Ditto.
(record_get_exec_direction, record_set_exec_direction): Remove.
(record_can_execute_reverse): New target method.
2008-10-06 Michael Snyder <msnyder@vmware.com>
* record.c (displaced_step_fixup): Remove.
(record_message_cleanups): Remove displaced step handling.
(record_message): Remove displaced step handling.
2008-10-06 Michael Snyder <msnyder@vmware.com>
* record.c (record_regcache_raw_write_regnum): Remove.
2008-10-05 Michael Snyder <msnyder@vmware.com>
* record.c, record.h: Rename execdir to exec_direction.
2008-10-02 Michael Snyder <msnyder@vmware.com>
* record.c (record_open): Call target_can_async_p() instead
of relying on a global variable.
* record.h (record_linux_async_permitted): Delete.
2008-09-19 Michael Snyder <msnyder@vmware.com>
* record.c (trivial): Fix two commas in a comment.
* record.c (record_wait): On end of record log, return
TARGET_WAITKIND_NO_HISTORY and let infrun decide what to do.
2008-09-06 Michael Snyder <msnyder@vmware.com>
* record.c: Comment and message string cleanup.
Add some function header comments.
2008-08-01 Michael Snyder <msnyder@specifix.com>
* record.c (_initialize_record): Clarify language in help
strings.
Fix up comment format (period must be followed by two spaces).
[-- Attachment #2: 3-record_target.txt --]
[-- Type: text/plain, Size: 37589 bytes --]
--- a/Makefile.in
+++ b/Makefile.in
@@ -657,7 +657,7 @@
valarith.c valops.c valprint.c value.c varobj.c vec.c \
wrapper.c \
xml-tdesc.c xml-support.c \
- inferior.c
+ inferior.c record.c
LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c
@@ -808,7 +808,7 @@
solib.o solib-null.o \
prologue-value.o memory-map.o xml-support.o \
target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
- inferior.o osdata.o
+ inferior.o osdata.o record.o
TSOBS = inflow.o
--- a//dev/null
+++ b/record.c
@@ -0,0 +1,1299 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "defs.h"
+#include "target.h"
+#include "gdbcmd.h"
+#include "regcache.h"
+#include "inferior.h"
+#include "gdbthread.h"
+#include "record.h"
+
+#include <signal.h>
+
+#define DEFAULT_RECORD_INSN_MAX_NUM 200000
+
+int record_debug = 0;
+
+record_t record_first;
+record_t *record_list = &record_first;
+record_t *record_arch_list_head = NULL;
+record_t *record_arch_list_tail = NULL;
+struct regcache *record_regcache = NULL;
+
+/* 1 ask user. 0 auto delete the last record_t. */
+static int record_stop_at_limit = 1;
+static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM;
+static int record_insn_num = 0;
+
+struct target_ops record_ops;
+static int record_resume_step = 0;
+static enum target_signal record_resume_siggnal;
+static int record_get_sig = 0;
+static sigset_t record_maskall;
+static int record_gdb_operation_disable = 0;
+int record_will_store_registers = 0;
+
+extern struct bp_location *bp_location_chain;
+
+/* The real beneath function pointers. */
+void (*record_beneath_to_resume) (ptid_t, int, enum target_signal);
+ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *);
+void (*record_beneath_to_store_registers) (struct regcache *, int regno);
+LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops,
+ enum target_object object,
+ const char *annex,
+ gdb_byte * readbuf,
+ const gdb_byte * writebuf,
+ ULONGEST offset, LONGEST len);
+int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *);
+int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
+
+static void
+record_list_release (record_t * rec)
+{
+ record_t *tmp;
+
+ if (!rec)
+ return;
+
+ while (rec->next)
+ {
+ rec = rec->next;
+ }
+
+ while (rec->prev)
+ {
+ tmp = rec;
+ rec = rec->prev;
+ if (tmp->type == record_reg)
+ {
+ xfree (tmp->u.reg.val);
+ }
+ else if (tmp->type == record_mem)
+ {
+ xfree (tmp->u.mem.val);
+ }
+ xfree (tmp);
+ }
+
+ if (rec != &record_first)
+ {
+ xfree (rec);
+ }
+}
+
+static void
+record_list_release_next (void)
+{
+ record_t *rec = record_list;
+ record_t *tmp = rec->next;
+ rec->next = NULL;
+ while (tmp)
+ {
+ rec = tmp->next;
+ if (tmp->type == record_reg)
+ {
+ record_insn_num--;
+ }
+ else if (tmp->type == record_reg)
+ {
+ xfree (tmp->u.reg.val);
+ }
+ else if (tmp->type == record_mem)
+ {
+ xfree (tmp->u.mem.val);
+ }
+ xfree (tmp);
+ tmp = rec;
+ }
+}
+
+static void
+record_list_release_first (void)
+{
+ record_t *tmp = NULL;
+ enum record_type type;
+
+ if (!record_first.next)
+ {
+ return;
+ }
+
+ while (1)
+ {
+ type = record_first.next->type;
+
+ if (type == record_reg)
+ {
+ xfree (record_first.next->u.reg.val);
+ }
+ else if (type == record_mem)
+ {
+ xfree (record_first.next->u.mem.val);
+ }
+ tmp = record_first.next;
+ record_first.next = tmp->next;
+ xfree (tmp);
+
+ if (!record_first.next)
+ {
+ gdb_assert (record_insn_num == 1);
+ break;
+ }
+
+ record_first.next->prev = &record_first;
+
+ if (type == record_end)
+ {
+ break;
+ }
+ }
+
+ record_insn_num--;
+}
+
+/* Add a record_t to record_arch_list. */
+static void
+record_arch_list_add (record_t * rec)
+{
+ if (record_debug > 1)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_arch_list_add 0x%s.\n",
+ paddr_nz ((CORE_ADDR)rec));
+ }
+
+ if (record_arch_list_tail)
+ {
+ record_arch_list_tail->next = rec;
+ rec->prev = record_arch_list_tail;
+ record_arch_list_tail = rec;
+ }
+ else
+ {
+ record_arch_list_head = rec;
+ record_arch_list_tail = rec;
+ }
+}
+
+/* Record the value of a register ("num") to record_arch_list. */
+int
+record_arch_list_add_reg (int num)
+{
+ record_t *rec;
+
+ if (record_debug > 1)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: add register num = %d to record list.\n",
+ num);
+ }
+
+ rec = (record_t *) xmalloc (sizeof (record_t));
+ rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE);
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_reg;
+ rec->u.reg.num = num;
+
+ regcache_raw_read (record_regcache, num, rec->u.reg.val);
+
+ record_arch_list_add (rec);
+
+ return (0);
+}
+
+/* Record the value of a region of memory whose address is "addr" and
+ length is "len" to record_arch_list. */
+
+int
+record_arch_list_add_mem (CORE_ADDR addr, int len)
+{
+ record_t *rec;
+
+ if (record_debug > 1)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: add mem addr = 0x%s len = %d to record list.\n",
+ paddr_nz (addr), len);
+ }
+
+ if (!addr)
+ {
+ return (0);
+ }
+
+ rec = (record_t *) xmalloc (sizeof (record_t));
+ rec->u.mem.val = (gdb_byte *) xmalloc (len);
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_mem;
+ rec->u.mem.addr = addr;
+ rec->u.mem.len = len;
+
+ if (target_read_memory (addr, rec->u.mem.val, len))
+ {
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: error reading memory at addr = 0x%s len = %d.\n",
+ paddr_nz (addr), len);
+ }
+ xfree (rec->u.mem.val);
+ xfree (rec);
+ return (-1);
+ }
+
+ record_arch_list_add (rec);
+
+ return (0);
+}
+
+/* Add a record_end type record_t to record_arch_list. */
+int
+record_arch_list_add_end (int need_dasm)
+{
+ record_t *rec;
+
+ if (record_debug > 1)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: add end need_dasm = %d to arch list.\n",
+ need_dasm);
+ }
+
+ rec = (record_t *) xmalloc (sizeof (record_t));
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_end;
+
+ rec->u.need_dasm = need_dasm;
+
+ record_arch_list_add (rec);
+
+ return (0);
+}
+
+static void
+record_check_insn_num (int set_terminal)
+{
+ if (record_insn_max_num)
+ {
+ gdb_assert (record_insn_num <= record_insn_max_num);
+ if (record_insn_num == record_insn_max_num)
+ {
+ /* Ask user what to do. */
+ if (record_stop_at_limit)
+ {
+ int q;
+ if (set_terminal)
+ target_terminal_ours ();
+ q = yquery (_("Do you want to auto delete previous execution log entries when record/replay buffer becomes full (record-stop-at-limit)?"));
+ if (set_terminal)
+ target_terminal_inferior ();
+ if (q)
+ {
+ record_stop_at_limit = 0;
+ }
+ else
+ {
+ error (_("Process record: inferior program stopped."));
+ }
+ }
+ }
+ }
+}
+
+/* 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_list_release (record_arch_list_tail);
+ set_executing (inferior_ptid, 0);
+ normal_stop ();
+}
+
+void
+record_message (struct gdbarch *gdbarch)
+{
+ int ret;
+ struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+
+ /* Check record_insn_num. */
+ record_check_insn_num (1);
+
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+ record_regcache = get_current_regcache ();
+
+ ret = gdbarch_process_record (gdbarch,
+ regcache_read_pc (record_regcache));
+ if (ret > 0)
+ error (_("Process record: inferior program stopped."));
+ if (ret < 0)
+ error (_("Process record: failed to record execution log."));
+
+ discard_cleanups (old_cleanups);
+
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ {
+ record_list_release_first ();
+ }
+ else
+ {
+ record_insn_num++;
+ }
+}
+
+/* Things to clean up if we error or QUIT out of function that set
+ record_gdb_operation_disable (ie. command that caused target_wait to be called). */
+static void
+record_gdb_operation_disable_cleanups (void *ignore)
+{
+ record_gdb_operation_disable = 0;
+}
+
+struct cleanup *
+record_gdb_operation_disable_set (void)
+{
+ struct cleanup *old_cleanups = make_cleanup (record_gdb_operation_disable_cleanups, 0);
+ record_gdb_operation_disable = 1;
+
+ return old_cleanups;
+}
+
+static void
+record_open (char *name, int from_tty)
+{
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+ }
+
+ /* check exec */
+ if (!target_has_execution)
+ {
+ error (_("Process record: the program is not being run."));
+ }
+ if (non_stop)
+ {
+ error (_("Process record target can't debug inferior in non-stop mode (non-stop)."));
+ }
+ if (target_async_permitted)
+ {
+ error (_("Process record target can't debug inferior in asynchronous mode (target-async)."));
+ }
+
+ if (!gdbarch_process_record_p (current_gdbarch))
+ {
+ error (_("Process record: the current architecture doesn't support record function."));
+ }
+
+ /* Check if record target is already running. */
+ if (TARGET_IS_PROCESS_RECORD)
+ {
+ if (!nquery
+ (_("Process record target already running, do you want to delete the old record log?")))
+ {
+ return;
+ }
+ }
+
+ push_target (&record_ops);
+
+ /* Reset */
+ record_insn_num = 0;
+ record_list = &record_first;
+ record_list->next = NULL;
+}
+
+static void
+record_close (int quitting)
+{
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
+ }
+ record_list_release (record_list);
+}
+
+static void
+record_resume (ptid_t ptid, int step, enum target_signal siggnal)
+{
+ record_resume_step = step;
+ record_resume_siggnal = siggnal;
+
+ if (!RECORD_IS_REPLAY)
+ {
+ record_message (current_gdbarch);
+ record_beneath_to_resume (ptid, 1, siggnal);
+ }
+}
+
+static void
+record_sig_handler (int signo)
+{
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n");
+ }
+ record_resume_step = 1;
+ record_get_sig = 1;
+}
+
+static void
+record_wait_cleanups (void *ignore)
+{
+ if (execution_direction == EXEC_REVERSE)
+ {
+ if (record_list->next)
+ {
+ record_list = record_list->next;
+ }
+ }
+ else
+ {
+ record_list = record_list->prev;
+ }
+ set_executing (inferior_ptid, 0);
+ normal_stop ();
+}
+
+/* record_wait
+ In replay mode, this function examines the recorded log and
+ determines where to stop. */
+
+static ptid_t
+record_wait (ptid_t ptid, struct target_waitstatus *status)
+{
+ struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
+
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_wait record_resume_step = %d\n",
+ record_resume_step);
+ }
+
+ if (!RECORD_IS_REPLAY)
+ {
+ if (record_resume_step)
+ {
+ /* This is a single step. */
+ return record_beneath_to_wait (ptid, status);
+ }
+ else
+ {
+ if (record_resume_step)
+ {
+ /* This is a single step. */
+ return record_beneath_to_wait (ptid, status);
+ }
+ else
+ {
+ /* This is not a single step. */
+ ptid_t ret;
+ int is_breakpoint = 1;
+ CORE_ADDR pc = 0;
+ int pc_is_read = 0;
+ struct bp_location *bl;
+ struct breakpoint *b;
+
+ do
+ {
+ ret = record_beneath_to_wait (ptid, status);
+
+ if (status->kind == TARGET_WAITKIND_STOPPED
+ && status->value.sig == TARGET_SIGNAL_TRAP)
+ {
+ /* Check if there is a breakpoint. */
+ pc_is_read = 0;
+ registers_changed ();
+ for (bl = bp_location_chain; bl; bl = bl->global_next)
+ {
+ b = bl->owner;
+ gdb_assert (b);
+ if (b->enable_state != bp_enabled
+ && b->enable_state != bp_permanent)
+ continue;
+ if (!pc_is_read)
+ {
+ pc =
+ regcache_read_pc (get_thread_regcache (ret));
+ pc_is_read = 1;
+ }
+ switch (b->type)
+ {
+ default:
+ if (bl->address == pc)
+ {
+ goto breakpoint;
+ }
+ break;
+
+ case bp_watchpoint:
+ /*XXX teawater: I still not very clear how to deal with it. */
+ goto breakpoint;
+ break;
+
+ case bp_catchpoint:
+ gdb_assert (b->ops != NULL
+ && b->ops->breakpoint_hit != NULL);
+ if (b->ops->breakpoint_hit (b))
+ {
+ goto breakpoint;
+ }
+ break;
+
+ case bp_hardware_watchpoint:
+ case bp_read_watchpoint:
+ case bp_access_watchpoint:
+ if (STOPPED_BY_WATCHPOINT (0))
+ {
+ goto breakpoint;
+ }
+ break;
+ }
+ }
+
+ /* There is not a breakpoint. */
+ record_message (current_gdbarch);
+ record_beneath_to_resume (ptid, 1,
+ record_resume_siggnal);
+ continue;
+ }
+
+ is_breakpoint = 0;
+
+ breakpoint:
+ /* Add gdbarch_decr_pc_after_break to pc because gdb will
+ expect the pc to be at address plus decr_pc_after_break
+ when the inferior stops at a breakpoint. */
+ if (is_breakpoint)
+ {
+ CORE_ADDR decr_pc_after_break =
+ gdbarch_decr_pc_after_break (current_gdbarch);
+ if (decr_pc_after_break)
+ {
+ if (!pc_is_read)
+ {
+ pc =
+ regcache_read_pc (get_thread_regcache (ret));
+ }
+ regcache_write_pc (get_thread_regcache (ret),
+ pc + decr_pc_after_break);
+ }
+ }
+
+ break;
+ }
+ while (1);
+
+ return ret;
+ }
+ }
+ }
+ else
+ {
+ struct sigaction act, old_act;
+ int need_dasm = 0;
+ struct regcache *regcache = get_current_regcache ();
+ int continue_flag = 1;
+ int first_record_end = 1;
+ struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0);
+ CORE_ADDR tmp_pc;
+
+ status->kind = TARGET_WAITKIND_STOPPED;
+
+ /* Check breakpoint when forward execute. */
+ if (execution_direction == EXEC_FORWARD)
+ {
+ tmp_pc = regcache_read_pc (regcache);
+ if (breakpoint_inserted_here_p (tmp_pc))
+ {
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: break at 0x%s.\n",
+ paddr_nz (tmp_pc));
+ }
+ if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+ && !record_resume_step)
+ {
+ regcache_write_pc (regcache,
+ tmp_pc +
+ gdbarch_decr_pc_after_break
+ (get_regcache_arch (regcache)));
+ }
+ goto replay_out;
+ }
+ }
+
+ record_get_sig = 0;
+ act.sa_handler = record_sig_handler;
+ act.sa_mask = record_maskall;
+ act.sa_flags = SA_RESTART;
+ if (sigaction (SIGINT, &act, &old_act))
+ {
+ perror_with_name (_("Process record: sigaction failed"));
+ }
+ /* If GDB is in terminal_inferior mode, it will not get the signal.
+ And in GDB replay mode, GDB doesn't need to be in terminal_inferior
+ mode, because inferior will not executed.
+ Then set it to terminal_ours to make GDB get the signal. */
+ target_terminal_ours ();
+
+ /* In EXEC_FORWARD mode, record_list points to the tail of prev
+ instruction. */
+ if (execution_direction == EXEC_FORWARD && record_list->next)
+ {
+ record_list = record_list->next;
+ }
+
+ /* Loop over the record_list, looking for the next place to
+ stop. */
+ do
+ {
+ /* Check for beginning and end of log. */
+ if (execution_direction == EXEC_REVERSE
+ && record_list == &record_first)
+ {
+ /* Hit beginning of record log in reverse. */
+ status->kind = TARGET_WAITKIND_NO_HISTORY;
+ break;
+ }
+ if (execution_direction != EXEC_REVERSE && !record_list->next)
+ {
+ /* Hit end of record log going forward. */
+ status->kind = TARGET_WAITKIND_NO_HISTORY;
+ break;
+ }
+
+ /* Set ptid, register and memory according to record_list. */
+ if (record_list->type == record_reg)
+ {
+ /* reg */
+ gdb_byte reg[MAX_REGISTER_SIZE];
+ if (record_debug > 1)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_reg 0x%s to inferior num = %d.\n",
+ paddr_nz ((CORE_ADDR)record_list),
+ record_list->u.reg.num);
+ }
+ regcache_cooked_read (regcache, record_list->u.reg.num, reg);
+ regcache_cooked_write (regcache, record_list->u.reg.num,
+ record_list->u.reg.val);
+ memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE);
+ }
+ else if (record_list->type == record_mem)
+ {
+ /* mem */
+ gdb_byte *mem = alloca (record_list->u.mem.len);
+ if (record_debug > 1)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_mem 0x%s to inferior addr = 0x%s len = %d.\n",
+ paddr_nz ((CORE_ADDR)record_list),
+ paddr_nz (record_list->u.mem.addr),
+ record_list->u.mem.len);
+ }
+
+ if (target_read_memory
+ (record_list->u.mem.addr, mem, record_list->u.mem.len))
+ {
+ error (_("Process record: error reading memory at addr = 0x%s len = %d."),
+ paddr_nz (record_list->u.mem.addr),
+ record_list->u.mem.len);
+ }
+
+ if (target_write_memory
+ (record_list->u.mem.addr, record_list->u.mem.val,
+ record_list->u.mem.len))
+ {
+ error (_
+ ("Process record: error writing memory at addr = 0x%s len = %d."),
+ paddr_nz (record_list->u.mem.addr),
+ record_list->u.mem.len);
+ }
+ memcpy (record_list->u.mem.val, mem, record_list->u.mem.len);
+ }
+ else
+ {
+ if (record_debug > 1)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_end 0x%s to inferior need_dasm = %d.\n",
+ paddr_nz ((CORE_ADDR)record_list),
+ record_list->u.need_dasm);
+ }
+
+ if (execution_direction == EXEC_FORWARD)
+ {
+ need_dasm = record_list->u.need_dasm;
+ }
+ if (need_dasm)
+ {
+ gdbarch_process_record_dasm (current_gdbarch);
+ }
+
+ if (first_record_end && execution_direction == EXEC_REVERSE)
+ {
+ /* When reverse excute, the first record_end is the part of
+ current instruction. */
+ first_record_end = 0;
+ }
+ else
+ {
+ /* In EXEC_REVERSE mode, this is the record_end of prev
+ instruction.
+ In EXEC_FORWARD mode, this is the record_end of current
+ instruction. */
+ /* step */
+ if (record_resume_step)
+ {
+ if (record_debug > 1)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: step.\n");
+ }
+ continue_flag = 0;
+ }
+
+ /* check breakpoint */
+ tmp_pc = regcache_read_pc (regcache);
+ if (breakpoint_inserted_here_p (tmp_pc))
+ {
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: break at 0x%s.\n",
+ paddr_nz (tmp_pc));
+ }
+ if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache))
+ && execution_direction == EXEC_FORWARD
+ && !record_resume_step)
+ {
+ regcache_write_pc (regcache,
+ tmp_pc +
+ gdbarch_decr_pc_after_break
+ (get_regcache_arch (regcache)));
+ }
+ continue_flag = 0;
+ }
+ }
+ if (execution_direction == EXEC_REVERSE)
+ {
+ need_dasm = record_list->u.need_dasm;
+ }
+ }
+
+next:
+ if (continue_flag)
+ {
+ if (execution_direction == EXEC_REVERSE)
+ {
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+ else
+ {
+ if (record_list->next)
+ record_list = record_list->next;
+ }
+ }
+ }
+ while (continue_flag);
+
+ if (sigaction (SIGALRM, &old_act, NULL))
+ {
+ perror_with_name (_("Process record: sigaction failed"));
+ }
+
+replay_out:
+ if (record_get_sig)
+ {
+ status->value.sig = TARGET_SIGNAL_INT;
+ }
+ else
+ {
+ status->value.sig = TARGET_SIGNAL_TRAP;
+ }
+
+ discard_cleanups (old_cleanups);
+ }
+
+ do_cleanups (set_cleanups);
+ return inferior_ptid;
+}
+
+static void
+record_disconnect (struct target_ops *target, char *args, int from_tty)
+{
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n");
+ }
+ unpush_target (&record_ops);
+ target_disconnect (args, from_tty);
+}
+
+static void
+record_detach (struct target_ops *ops, char *args, int from_tty)
+{
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n");
+ }
+ unpush_target (&record_ops);
+ target_detach (args, from_tty);
+}
+
+static void
+record_mourn_inferior (struct target_ops *ops)
+{
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_mourn_inferior\n");
+ }
+ unpush_target (&record_ops);
+ target_mourn_inferior ();
+}
+
+/* Close process record target before killing the inferior process. */
+static void
+record_kill (void)
+{
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
+ }
+ unpush_target (&record_ops);
+ target_kill ();
+}
+
+/* Record registers change (by user or by GDB) to list as an instruction. */
+static void
+record_registers_change (struct regcache *regcache, int regnum)
+{
+ /* Check record_insn_num. */
+ record_check_insn_num (0);
+
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+
+ record_regcache = get_current_regcache ();
+
+ if (regnum < 0)
+ {
+ int i;
+ for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++)
+ {
+ if (record_arch_list_add_reg (i))
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ }
+ }
+ else
+ {
+ if (record_arch_list_add_reg (regnum))
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ }
+ if (record_arch_list_add_end (0))
+ {
+ record_list_release (record_arch_list_tail);
+ error (_("Process record: failed to record execution log."));
+ }
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ {
+ record_list_release_first ();
+ }
+ else
+ {
+ record_insn_num++;
+ }
+}
+
+static void
+record_store_registers (struct regcache *regcache, int regno)
+{
+ if (!record_gdb_operation_disable)
+ {
+ if (RECORD_IS_REPLAY)
+ {
+ int n;
+ struct cleanup *old_cleanups;
+
+ /* Let user choose if he wants to write register or not. */
+ if (regno < 0)
+ {
+ n =
+ nquery (_
+ ("Because GDB is in replay mode, changing the value of a register will make the execution log unusable from this point onward. Change all registers?"));
+ }
+ else
+ {
+ n =
+ nquery (_
+ ("Because GDB is in replay mode, changing the value of a register will make the execution log unusable from this point onward. Change register %s?"),
+ gdbarch_register_name (get_regcache_arch (regcache),
+ regno));
+ }
+
+ if (!n)
+ {
+ /* Invalidate the value of regcache that was set in function
+ "regcache_raw_write". */
+ if (regno < 0)
+ {
+ int i;
+ for (i = 0;
+ i < gdbarch_num_regs (get_regcache_arch (regcache));
+ i++)
+ {
+ regcache_invalidate (regcache, i);
+ }
+ }
+ else
+ {
+ regcache_invalidate (regcache, regno);
+ }
+
+ error (_("Process record canceled the operation."));
+ }
+
+ /* Destroy the record from here forward. */
+ record_list_release_next ();
+ }
+
+ record_registers_change (regcache, regno);
+ }
+ record_beneath_to_store_registers (regcache, regno);
+}
+
+/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY.
+ In replay mode, we cannot write memory unles we are willing to
+ invalidate the record/replay log from this point forward. */
+
+static LONGEST
+record_xfer_partial (struct target_ops *ops, enum target_object object,
+ const char *annex, gdb_byte * readbuf,
+ const gdb_byte * writebuf, ULONGEST offset, LONGEST len)
+{
+ if (!record_gdb_operation_disable
+ && (object == TARGET_OBJECT_MEMORY
+ || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+ {
+ if (RECORD_IS_REPLAY)
+ {
+ /* Let user choose if he wants to write memory or not. */
+ if (!nquery (_("Because GDB is in replay mode, writing to memory will make the execution log unusable from this point onward. Write memory at address 0x%s?"),
+ paddr_nz (offset)))
+ {
+ return -1;
+ }
+
+ /* Destroy the record from here forward. */
+ record_list_release_next ();
+ }
+
+ /* Check record_insn_num */
+ record_check_insn_num (0);
+
+ /* Record registers change to list as an instruction. */
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+ if (record_arch_list_add_mem (offset, len))
+ {
+ record_list_release (record_arch_list_tail);
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ _
+ ("Process record: failed to record execution log."));
+ }
+ return -1;
+ }
+ if (record_arch_list_add_end (0))
+ {
+ record_list_release (record_arch_list_tail);
+ if (record_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ _
+ ("Process record: failed to record execution log."));
+ }
+ return -1;
+ }
+ record_list->next = record_arch_list_head;
+ record_arch_list_head->prev = record_list;
+ record_list = record_arch_list_tail;
+
+ if (record_insn_num == record_insn_max_num && record_insn_max_num)
+ {
+ record_list_release_first ();
+ }
+ else
+ {
+ record_insn_num++;
+ }
+ }
+
+ return record_beneath_to_xfer_partial (ops, object, annex, readbuf,
+ writebuf, offset, len);
+}
+
+/* record_insert_breakpoint
+ record_remove_breakpoint
+ Behavior is conditional on RECORD_IS_REPLAY.
+ We will not actually insert or remove breakpoints when replaying,
+ nor when recording. */
+
+static int
+record_insert_breakpoint (struct bp_target_info *bp_tgt)
+{
+ if (!RECORD_IS_REPLAY)
+ {
+ struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+ int ret = record_beneath_to_insert_breakpoint (bp_tgt);
+
+ do_cleanups (old_cleanups);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+record_remove_breakpoint (struct bp_target_info *bp_tgt)
+{
+ if (!RECORD_IS_REPLAY)
+ {
+ struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
+ int ret = record_beneath_to_remove_breakpoint (bp_tgt);
+
+ do_cleanups (old_cleanups);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+record_can_execute_reverse (void)
+{
+ return 1;
+}
+
+static void
+init_record_ops (void)
+{
+ record_ops.to_shortname = "record";
+ record_ops.to_longname = "Process record and replay target";
+ record_ops.to_doc =
+ "Log program while executing and replay execution from log.";
+ record_ops.to_open = record_open;
+ record_ops.to_close = record_close;
+ record_ops.to_resume = record_resume;
+ record_ops.to_wait = record_wait;
+ record_ops.to_disconnect = record_disconnect;
+ record_ops.to_detach = record_detach;
+ record_ops.to_mourn_inferior = record_mourn_inferior;
+ record_ops.to_kill = record_kill;
+ record_ops.to_create_inferior = find_default_create_inferior; /* Make record support command "run". */
+ record_ops.to_store_registers = record_store_registers;
+ record_ops.to_xfer_partial = record_xfer_partial;
+ record_ops.to_insert_breakpoint = record_insert_breakpoint;
+ record_ops.to_remove_breakpoint = record_remove_breakpoint;
+ record_ops.to_can_execute_reverse = record_can_execute_reverse;
+ record_ops.to_stratum = record_stratum;
+ record_ops.to_magic = OPS_MAGIC;
+}
+
+static void
+show_record_debug (struct ui_file *file, int from_tty,
+ struct cmd_list_element *c, const char *value)
+{
+ fprintf_filtered (file, _("Debugging of process record target is %s.\n"),
+ value);
+}
+
+/* cmd_record_start -- alias for "target record". */
+
+static void
+cmd_record_start (char *args, int from_tty)
+{
+ execute_command ("target record", from_tty);
+}
+
+/* cmd_record_delete -- truncate the record log from the present point
+ of replay until the end. */
+
+static void
+cmd_record_delete (char *args, int from_tty)
+{
+ if (TARGET_IS_PROCESS_RECORD)
+ {
+ if (RECORD_IS_REPLAY)
+ {
+ if (!from_tty || query (_("Delete the log from this point forward and begin to record the running message at current PC?")))
+ {
+ record_list_release_next ();
+ }
+ }
+ else
+ {
+ printf_unfiltered (_("Already at end of record list.\n"));
+ }
+
+ }
+ else
+ {
+ printf_unfiltered (_("Process record is not started.\n"));
+ }
+}
+
+/* cmd_record_stop -- implement the "stoprecord" command. */
+
+static void
+cmd_record_stop (char *args, int from_tty)
+{
+ if (TARGET_IS_PROCESS_RECORD)
+ {
+ if (!record_list || !from_tty || query (_("Delete recorded log and stop recording?")))
+ {
+ unpush_target (&record_ops);
+ }
+ }
+ else
+ {
+ printf_unfiltered (_("Process record is not started.\n"));
+ }
+}
+
+/* set_record_insn_max_num -- set upper limit of record log size. */
+
+static void
+set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c)
+{
+ if (record_insn_num > record_insn_max_num && record_insn_max_num)
+ {
+ printf_unfiltered (_("Record instructions number is bigger than record instructions max number. Auto delete the first ones?\n"));
+
+ while (record_insn_num > record_insn_max_num)
+ {
+ record_list_release_first ();
+ }
+ }
+}
+
+/* show_record_insn_number -- print the current index
+ into the record log (number of insns recorded so far). */
+
+static void
+show_record_insn_number (char *ignore, int from_tty)
+{
+ printf_unfiltered (_("Record instruction number is %d.\n"),
+ record_insn_num);
+}
+
+void
+_initialize_record (void)
+{
+ /* Init record_maskall. */
+ if (sigfillset (&record_maskall) == -1)
+ {
+ perror_with_name (_("Process record: sigfillset failed"));
+ }
+
+ /* Init record_first. */
+ record_first.prev = NULL;
+ record_first.next = NULL;
+ record_first.type = record_end;
+ record_first.u.need_dasm = 0;
+
+ init_record_ops ();
+ add_target (&record_ops);
+
+ add_setshow_zinteger_cmd ("record", no_class, &record_debug,
+ _("Set debugging of record/replay feature."),
+ _("Show debugging of record/replay feature."),
+ _
+ ("When enabled, debugging output for record/replay feature is displayed."),
+ NULL, show_record_debug, &setdebuglist,
+ &showdebuglist);
+
+ add_com ("record", class_obscure, cmd_record_start,
+ _("Abbreviated form of \"target record\" command."));
+
+ add_com_alias ("rec", "record", class_obscure, 1);
+
+ /* XXX: I try to use some simple commands such as "disconnect" and
+ "detach" to support this functions. But these commands all have
+ other affect to GDB such as call function "no_shared_libraries".
+ So I add special commands to GDB. */
+ add_com ("delrecord", class_obscure, cmd_record_delete,
+ _("Delete the rest of execution log and start recording it anew."));
+ add_com_alias ("dr", "delrecord", class_obscure, 1);
+ add_com ("stoprecord", class_obscure, cmd_record_stop,
+ _("Stop the record/replay target."));
+ add_com_alias ("sr", "stoprecord", class_obscure, 1);
+
+ /* Record instructions number limit command. */
+ add_setshow_boolean_cmd ("record-stop-at-limit", no_class,
+ &record_stop_at_limit,
+ _("Set whether record/replay stop when record/replay buffer becomes full."),
+ _("Show whether record/replay stop when record/replay buffer becomes full."), _("\
+Enable is default value.\n\
+When enabled, if the record/replay buffer becomes full,\n\
+ask user what to do.\n\
+When disabled, if the record/replay buffer becomes full,\n\
+delete it and start new recording."), NULL, NULL, &setlist, &showlist);
+ add_setshow_zinteger_cmd ("record-insn-number-max", no_class,
+ &record_insn_max_num,
+ _("Set record/replay buffer limit."),
+ _("Show record/replay buffer limit."), _("\
+Set the maximum number of instructions to be stored in the\n\
+record/replay buffer. Zero means unlimited (default 200000)."),
+ set_record_insn_max_num,
+ NULL, &setlist, &showlist);
+ add_info ("record-insn-number", show_record_insn_number, _("\
+Show the current number of instructions in the record/replay buffer."));
+}
--- a//dev/null
+++ b/record.h
@@ -0,0 +1,100 @@
+/* Process record and replay target for GDB, the GNU debugger.
+
+ Copyright (C) 2008 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/>. */
+
+#ifndef _RECORD_H_
+#define _RECORD_H_
+
+#define TARGET_IS_PROCESS_RECORD \
+ (current_target.beneath == &record_ops)
+#define RECORD_IS_REPLAY \
+ (record_list->next || execution_direction == EXEC_REVERSE)
+
+typedef struct record_reg_s
+{
+ int num;
+ gdb_byte *val;
+} record_reg_t;
+
+typedef struct record_mem_s
+{
+ CORE_ADDR addr;
+ int len;
+ gdb_byte *val;
+} record_mem_t;
+
+enum record_type
+{
+ record_end = 0,
+ record_reg,
+ record_mem
+};
+
+/* This is the core struct of record function.
+
+ An entity of record_t is a record of the value change of a register
+ ("record_reg") or a part of memory ("record_mem"). And each
+ instruction must has a record_t ("record_end") that points out this
+ is the last record_t of this instruction.
+
+ Each record_t is linked to "record_list" by "prev" and "next".
+ */
+typedef struct record_s
+{
+ struct record_s *prev;
+ struct record_s *next;
+ enum record_type type;
+ union
+ {
+ /* reg */
+ record_reg_t reg;
+ /* mem */
+ record_mem_t mem;
+ /* end */
+ int need_dasm;
+ } u;
+} record_t;
+
+extern int record_debug;
+extern record_t *record_list;
+extern record_t *record_arch_list_head;
+extern record_t *record_arch_list_tail;
+extern struct regcache *record_regcache;
+
+extern struct target_ops record_ops;
+
+extern int record_arch_list_add_reg (int num);
+extern int record_arch_list_add_mem (CORE_ADDR addr, int len);
+extern int record_arch_list_add_end (int need_dasm);
+extern void record_message (struct gdbarch *gdbarch);
+extern struct cleanup * record_gdb_operation_disable_set (void);
+
+extern void (*record_beneath_to_resume) (ptid_t, int, enum target_signal);
+extern ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *);
+extern void (*record_beneath_to_store_registers) (struct regcache *, int regno);
+extern LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops,
+ enum target_object object,
+ const char *annex,
+ gdb_byte * readbuf,
+ const gdb_byte * writebuf,
+ ULONGEST offset,
+ LONGEST len);
+extern int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *);
+extern int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *);
+
+#endif /* _RECORD_H_ */
^ permalink raw reply [flat|nested] 38+ messages in thread* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-08 5:46 [RFA] Submit process record and replay third time, 3/9 teawater @ 2009-01-13 3:06 ` teawater 2009-01-20 18:32 ` Marc Khouzam 2009-01-22 13:23 ` Pedro Alves 1 sibling, 1 reply; 38+ messages in thread From: teawater @ 2009-01-13 3:06 UTC (permalink / raw) To: gdb-patches [-- Attachment #1: Type: text/plain, Size: 2689 bytes --] Update follow the cvs-head. On Thu, Jan 8, 2009 at 13:46, teawater <teawater@gmail.com> wrote: > > This patch add the process record and replay target. This is the core > part of process record and replay. > > 2009-01-08 Hui Zhu <teawater@gmail.com> > > Process record and replay target. > > * Makefile.in (record.c): New file. > * record.c, record.h: New file. > > 2008-12-28 Michael Snyder <msnyder@vmware.com> > * Comments, spelling, white space clean-ups. > > 2008-12-26 Michael Snyder <msnyder@vmware.com> > * record.h: Don't export record_not_record. > * record.c (record_not_record): Rename to in_record_wait. > (record_not_record_set): Rename to in_record_wait_set. > (record_not_record_cleanup): Rename to in_record_wait_cleanup. > (record_store_registers): Check in_record_wait flag. > (record_xfer_partial): Ditto. > > 2008-10-07 Michael Snyder <msnyder@vmware.com> > * record.h (record_exec_direction): Delete. > (RECORD_IS_REPLAY): Consult infrun global direction variable. > * record.c: (record_wait_cleanups): Use infrun state variable. > (record_wait): Ditto. > (record_get_exec_direction, record_set_exec_direction): Remove. > (record_can_execute_reverse): New target method. > > 2008-10-06 Michael Snyder <msnyder@vmware.com> > * record.c (displaced_step_fixup): Remove. > (record_message_cleanups): Remove displaced step handling. > (record_message): Remove displaced step handling. > > 2008-10-06 Michael Snyder <msnyder@vmware.com> > * record.c (record_regcache_raw_write_regnum): Remove. > > 2008-10-05 Michael Snyder <msnyder@vmware.com> > * record.c, record.h: Rename execdir to exec_direction. > > 2008-10-02 Michael Snyder <msnyder@vmware.com> > * record.c (record_open): Call target_can_async_p() instead > of relying on a global variable. > * record.h (record_linux_async_permitted): Delete. > > 2008-09-19 Michael Snyder <msnyder@vmware.com> > * record.c (trivial): Fix two commas in a comment. > * record.c (record_wait): On end of record log, return > TARGET_WAITKIND_NO_HISTORY and let infrun decide what to do. > > 2008-09-06 Michael Snyder <msnyder@vmware.com> > * record.c: Comment and message string cleanup. > Add some function header comments. > > 2008-08-01 Michael Snyder <msnyder@specifix.com> > * record.c (_initialize_record): Clarify language in help > strings. > Fix up comment format (period must be followed by two spaces). [-- Attachment #2: 3-record_target.txt --] [-- Type: text/plain, Size: 37589 bytes --] --- a/Makefile.in +++ b/Makefile.in @@ -658,7 +658,7 @@ valarith.c valops.c valprint.c value.c varobj.c vec.c \ wrapper.c \ xml-tdesc.c xml-support.c \ - inferior.c + inferior.c record.c LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c @@ -809,7 +809,7 @@ solib.o solib-null.o \ prologue-value.o memory-map.o xml-support.o \ target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ - inferior.o osdata.o + inferior.o osdata.o record.o TSOBS = inflow.o --- a//dev/null +++ b/record.c @@ -0,0 +1,1299 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "target.h" +#include "gdbcmd.h" +#include "regcache.h" +#include "inferior.h" +#include "gdbthread.h" +#include "record.h" + +#include <signal.h> + +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 + +int record_debug = 0; + +record_t record_first; +record_t *record_list = &record_first; +record_t *record_arch_list_head = NULL; +record_t *record_arch_list_tail = NULL; +struct regcache *record_regcache = NULL; + +/* 1 ask user. 0 auto delete the last record_t. */ +static int record_stop_at_limit = 1; +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; +static int record_insn_num = 0; + +struct target_ops record_ops; +static int record_resume_step = 0; +static enum target_signal record_resume_siggnal; +static int record_get_sig = 0; +static sigset_t record_maskall; +static int record_gdb_operation_disable = 0; +int record_will_store_registers = 0; + +extern struct bp_location *bp_location_chain; + +/* The real beneath function pointers. */ +void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); +ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); +void (*record_beneath_to_store_registers) (struct regcache *, int regno); +LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, LONGEST len); +int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); +int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); + +static void +record_list_release (record_t * rec) +{ + record_t *tmp; + + if (!rec) + return; + + while (rec->next) + { + rec = rec->next; + } + + while (rec->prev) + { + tmp = rec; + rec = rec->prev; + if (tmp->type == record_reg) + { + xfree (tmp->u.reg.val); + } + else if (tmp->type == record_mem) + { + xfree (tmp->u.mem.val); + } + xfree (tmp); + } + + if (rec != &record_first) + { + xfree (rec); + } +} + +static void +record_list_release_next (void) +{ + record_t *rec = record_list; + record_t *tmp = rec->next; + rec->next = NULL; + while (tmp) + { + rec = tmp->next; + if (tmp->type == record_reg) + { + record_insn_num--; + } + else if (tmp->type == record_reg) + { + xfree (tmp->u.reg.val); + } + else if (tmp->type == record_mem) + { + xfree (tmp->u.mem.val); + } + xfree (tmp); + tmp = rec; + } +} + +static void +record_list_release_first (void) +{ + record_t *tmp = NULL; + enum record_type type; + + if (!record_first.next) + { + return; + } + + while (1) + { + type = record_first.next->type; + + if (type == record_reg) + { + xfree (record_first.next->u.reg.val); + } + else if (type == record_mem) + { + xfree (record_first.next->u.mem.val); + } + tmp = record_first.next; + record_first.next = tmp->next; + xfree (tmp); + + if (!record_first.next) + { + gdb_assert (record_insn_num == 1); + break; + } + + record_first.next->prev = &record_first; + + if (type == record_end) + { + break; + } + } + + record_insn_num--; +} + +/* Add a record_t to record_arch_list. */ +static void +record_arch_list_add (record_t * rec) +{ + if (record_debug > 1) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: record_arch_list_add 0x%s.\n", + paddr_nz ((CORE_ADDR)rec)); + } + + if (record_arch_list_tail) + { + record_arch_list_tail->next = rec; + rec->prev = record_arch_list_tail; + record_arch_list_tail = rec; + } + else + { + record_arch_list_head = rec; + record_arch_list_tail = rec; + } +} + +/* Record the value of a register ("num") to record_arch_list. */ +int +record_arch_list_add_reg (int num) +{ + record_t *rec; + + if (record_debug > 1) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: add register num = %d to record list.\n", + num); + } + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + rec->u.reg.num = num; + + regcache_raw_read (record_regcache, num, rec->u.reg.val); + + record_arch_list_add (rec); + + return (0); +} + +/* Record the value of a region of memory whose address is "addr" and + length is "len" to record_arch_list. */ + +int +record_arch_list_add_mem (CORE_ADDR addr, int len) +{ + record_t *rec; + + if (record_debug > 1) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: add mem addr = 0x%s len = %d to record list.\n", + paddr_nz (addr), len); + } + + if (!addr) + { + return (0); + } + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.mem.val = (gdb_byte *) xmalloc (len); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + rec->u.mem.addr = addr; + rec->u.mem.len = len; + + if (target_read_memory (addr, rec->u.mem.val, len)) + { + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: error reading memory at addr = 0x%s len = %d.\n", + paddr_nz (addr), len); + } + xfree (rec->u.mem.val); + xfree (rec); + return (-1); + } + + record_arch_list_add (rec); + + return (0); +} + +/* Add a record_end type record_t to record_arch_list. */ +int +record_arch_list_add_end (int need_dasm) +{ + record_t *rec; + + if (record_debug > 1) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: add end need_dasm = %d to arch list.\n", + need_dasm); + } + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + + rec->u.need_dasm = need_dasm; + + record_arch_list_add (rec); + + return (0); +} + +static void +record_check_insn_num (int set_terminal) +{ + if (record_insn_max_num) + { + gdb_assert (record_insn_num <= record_insn_max_num); + if (record_insn_num == record_insn_max_num) + { + /* Ask user what to do. */ + if (record_stop_at_limit) + { + int q; + if (set_terminal) + target_terminal_ours (); + q = yquery (_("Do you want to auto delete previous execution log entries when record/replay buffer becomes full (record-stop-at-limit)?")); + if (set_terminal) + target_terminal_inferior (); + if (q) + { + record_stop_at_limit = 0; + } + else + { + error (_("Process record: inferior program stopped.")); + } + } + } + } +} + +/* 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_list_release (record_arch_list_tail); + set_executing (inferior_ptid, 0); + normal_stop (); +} + +void +record_message (struct gdbarch *gdbarch) +{ + int ret; + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + + /* Check record_insn_num. */ + record_check_insn_num (1); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + record_regcache = get_current_regcache (); + + ret = gdbarch_process_record (gdbarch, + regcache_read_pc (record_regcache)); + if (ret > 0) + error (_("Process record: inferior program stopped.")); + if (ret < 0) + error (_("Process record: failed to record execution log.")); + + discard_cleanups (old_cleanups); + + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + { + record_list_release_first (); + } + else + { + record_insn_num++; + } +} + +/* Things to clean up if we error or QUIT out of function that set + record_gdb_operation_disable (ie. command that caused target_wait to be called). */ +static void +record_gdb_operation_disable_cleanups (void *ignore) +{ + record_gdb_operation_disable = 0; +} + +struct cleanup * +record_gdb_operation_disable_set (void) +{ + struct cleanup *old_cleanups = make_cleanup (record_gdb_operation_disable_cleanups, 0); + record_gdb_operation_disable = 1; + + return old_cleanups; +} + +static void +record_open (char *name, int from_tty) +{ + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + } + + /* check exec */ + if (!target_has_execution) + { + error (_("Process record: the program is not being run.")); + } + if (non_stop) + { + error (_("Process record target can't debug inferior in non-stop mode (non-stop).")); + } + if (target_async_permitted) + { + error (_("Process record target can't debug inferior in asynchronous mode (target-async).")); + } + + if (!gdbarch_process_record_p (current_gdbarch)) + { + error (_("Process record: the current architecture doesn't support record function.")); + } + + /* Check if record target is already running. */ + if (TARGET_IS_PROCESS_RECORD) + { + if (!nquery + (_("Process record target already running, do you want to delete the old record log?"))) + { + return; + } + } + + push_target (&record_ops); + + /* Reset */ + record_insn_num = 0; + record_list = &record_first; + record_list->next = NULL; +} + +static void +record_close (int quitting) +{ + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); + } + record_list_release (record_list); +} + +static void +record_resume (ptid_t ptid, int step, enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; + + if (!RECORD_IS_REPLAY) + { + record_message (current_gdbarch); + record_beneath_to_resume (ptid, 1, siggnal); + } +} + +static void +record_sig_handler (int signo) +{ + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); + } + record_resume_step = 1; + record_get_sig = 1; +} + +static void +record_wait_cleanups (void *ignore) +{ + if (execution_direction == EXEC_REVERSE) + { + if (record_list->next) + { + record_list = record_list->next; + } + } + else + { + record_list = record_list->prev; + } + set_executing (inferior_ptid, 0); + normal_stop (); +} + +/* record_wait + In replay mode, this function examines the recorded log and + determines where to stop. */ + +static ptid_t +record_wait (ptid_t ptid, struct target_waitstatus *status) +{ + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); + + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: record_wait record_resume_step = %d\n", + record_resume_step); + } + + if (!RECORD_IS_REPLAY) + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (ptid, status); + } + else + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (ptid, status); + } + else + { + /* This is not a single step. */ + ptid_t ret; + int is_breakpoint = 1; + CORE_ADDR pc = 0; + int pc_is_read = 0; + struct bp_location *bl; + struct breakpoint *b; + + do + { + ret = record_beneath_to_wait (ptid, status); + + if (status->kind == TARGET_WAITKIND_STOPPED + && status->value.sig == TARGET_SIGNAL_TRAP) + { + /* Check if there is a breakpoint. */ + pc_is_read = 0; + registers_changed (); + for (bl = bp_location_chain; bl; bl = bl->global_next) + { + b = bl->owner; + gdb_assert (b); + if (b->enable_state != bp_enabled + && b->enable_state != bp_permanent) + continue; + if (!pc_is_read) + { + pc = + regcache_read_pc (get_thread_regcache (ret)); + pc_is_read = 1; + } + switch (b->type) + { + default: + if (bl->address == pc) + { + goto breakpoint; + } + break; + + case bp_watchpoint: + /*XXX teawater: I still not very clear how to deal with it. */ + goto breakpoint; + break; + + case bp_catchpoint: + gdb_assert (b->ops != NULL + && b->ops->breakpoint_hit != NULL); + if (b->ops->breakpoint_hit (b)) + { + goto breakpoint; + } + break; + + case bp_hardware_watchpoint: + case bp_read_watchpoint: + case bp_access_watchpoint: + if (STOPPED_BY_WATCHPOINT (0)) + { + goto breakpoint; + } + break; + } + } + + /* There is not a breakpoint. */ + record_message (current_gdbarch); + record_beneath_to_resume (ptid, 1, + record_resume_siggnal); + continue; + } + + is_breakpoint = 0; + + breakpoint: + /* Add gdbarch_decr_pc_after_break to pc because gdb will + expect the pc to be at address plus decr_pc_after_break + when the inferior stops at a breakpoint. */ + if (is_breakpoint) + { + CORE_ADDR decr_pc_after_break = + gdbarch_decr_pc_after_break (current_gdbarch); + if (decr_pc_after_break) + { + if (!pc_is_read) + { + pc = + regcache_read_pc (get_thread_regcache (ret)); + } + regcache_write_pc (get_thread_regcache (ret), + pc + decr_pc_after_break); + } + } + + break; + } + while (1); + + return ret; + } + } + } + else + { + struct sigaction act, old_act; + int need_dasm = 0; + struct regcache *regcache = get_current_regcache (); + int continue_flag = 1; + int first_record_end = 1; + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); + CORE_ADDR tmp_pc; + + status->kind = TARGET_WAITKIND_STOPPED; + + /* Check breakpoint when forward execute. */ + if (execution_direction == EXEC_FORWARD) + { + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: break at 0x%s.\n", + paddr_nz (tmp_pc)); + } + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && !record_resume_step) + { + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + } + goto replay_out; + } + } + + record_get_sig = 0; + act.sa_handler = record_sig_handler; + act.sa_mask = record_maskall; + act.sa_flags = SA_RESTART; + if (sigaction (SIGINT, &act, &old_act)) + { + perror_with_name (_("Process record: sigaction failed")); + } + /* If GDB is in terminal_inferior mode, it will not get the signal. + And in GDB replay mode, GDB doesn't need to be in terminal_inferior + mode, because inferior will not executed. + Then set it to terminal_ours to make GDB get the signal. */ + target_terminal_ours (); + + /* In EXEC_FORWARD mode, record_list points to the tail of prev + instruction. */ + if (execution_direction == EXEC_FORWARD && record_list->next) + { + record_list = record_list->next; + } + + /* Loop over the record_list, looking for the next place to + stop. */ + do + { + /* Check for beginning and end of log. */ + if (execution_direction == EXEC_REVERSE + && record_list == &record_first) + { + /* Hit beginning of record log in reverse. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + if (execution_direction != EXEC_REVERSE && !record_list->next) + { + /* Hit end of record log going forward. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + + /* Set ptid, register and memory according to record_list. */ + if (record_list->type == record_reg) + { + /* reg */ + gdb_byte reg[MAX_REGISTER_SIZE]; + if (record_debug > 1) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg 0x%s to inferior num = %d.\n", + paddr_nz ((CORE_ADDR)record_list), + record_list->u.reg.num); + } + regcache_cooked_read (regcache, record_list->u.reg.num, reg); + regcache_cooked_write (regcache, record_list->u.reg.num, + record_list->u.reg.val); + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); + } + else if (record_list->type == record_mem) + { + /* mem */ + gdb_byte *mem = alloca (record_list->u.mem.len); + if (record_debug > 1) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem 0x%s to inferior addr = 0x%s len = %d.\n", + paddr_nz ((CORE_ADDR)record_list), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + } + + if (target_read_memory + (record_list->u.mem.addr, mem, record_list->u.mem.len)) + { + error (_("Process record: error reading memory at addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + } + + if (target_write_memory + (record_list->u.mem.addr, record_list->u.mem.val, + record_list->u.mem.len)) + { + error (_ + ("Process record: error writing memory at addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + } + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); + } + else + { + if (record_debug > 1) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: record_end 0x%s to inferior need_dasm = %d.\n", + paddr_nz ((CORE_ADDR)record_list), + record_list->u.need_dasm); + } + + if (execution_direction == EXEC_FORWARD) + { + need_dasm = record_list->u.need_dasm; + } + if (need_dasm) + { + gdbarch_process_record_dasm (current_gdbarch); + } + + if (first_record_end && execution_direction == EXEC_REVERSE) + { + /* When reverse excute, the first record_end is the part of + current instruction. */ + first_record_end = 0; + } + else + { + /* In EXEC_REVERSE mode, this is the record_end of prev + instruction. + In EXEC_FORWARD mode, this is the record_end of current + instruction. */ + /* step */ + if (record_resume_step) + { + if (record_debug > 1) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: step.\n"); + } + continue_flag = 0; + } + + /* check breakpoint */ + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, + "Process record: break at 0x%s.\n", + paddr_nz (tmp_pc)); + } + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && execution_direction == EXEC_FORWARD + && !record_resume_step) + { + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + } + continue_flag = 0; + } + } + if (execution_direction == EXEC_REVERSE) + { + need_dasm = record_list->u.need_dasm; + } + } + +next: + if (continue_flag) + { + if (execution_direction == EXEC_REVERSE) + { + if (record_list->prev) + record_list = record_list->prev; + } + else + { + if (record_list->next) + record_list = record_list->next; + } + } + } + while (continue_flag); + + if (sigaction (SIGALRM, &old_act, NULL)) + { + perror_with_name (_("Process record: sigaction failed")); + } + +replay_out: + if (record_get_sig) + { + status->value.sig = TARGET_SIGNAL_INT; + } + else + { + status->value.sig = TARGET_SIGNAL_TRAP; + } + + discard_cleanups (old_cleanups); + } + + do_cleanups (set_cleanups); + return inferior_ptid; +} + +static void +record_disconnect (struct target_ops *target, char *args, int from_tty) +{ + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); + } + unpush_target (&record_ops); + target_disconnect (args, from_tty); +} + +static void +record_detach (struct target_ops *ops, char *args, int from_tty) +{ + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); + } + unpush_target (&record_ops); + target_detach (args, from_tty); +} + +static void +record_mourn_inferior (struct target_ops *ops) +{ + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, "Process record: record_mourn_inferior\n"); + } + unpush_target (&record_ops); + target_mourn_inferior (); +} + +/* Close process record target before killing the inferior process. */ +static void +record_kill (void) +{ + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); + } + unpush_target (&record_ops); + target_kill (); +} + +/* Record registers change (by user or by GDB) to list as an instruction. */ +static void +record_registers_change (struct regcache *regcache, int regnum) +{ + /* Check record_insn_num. */ + record_check_insn_num (0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + record_regcache = get_current_regcache (); + + if (regnum < 0) + { + int i; + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) + { + if (record_arch_list_add_reg (i)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + } + else + { + if (record_arch_list_add_reg (regnum)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + { + record_list_release_first (); + } + else + { + record_insn_num++; + } +} + +static void +record_store_registers (struct regcache *regcache, int regno) +{ + if (!record_gdb_operation_disable) + { + if (RECORD_IS_REPLAY) + { + int n; + struct cleanup *old_cleanups; + + /* Let user choose if he wants to write register or not. */ + if (regno < 0) + { + n = + nquery (_ + ("Because GDB is in replay mode, changing the value of a register will make the execution log unusable from this point onward. Change all registers?")); + } + else + { + n = + nquery (_ + ("Because GDB is in replay mode, changing the value of a register will make the execution log unusable from this point onward. Change register %s?"), + gdbarch_register_name (get_regcache_arch (regcache), + regno)); + } + + if (!n) + { + /* Invalidate the value of regcache that was set in function + "regcache_raw_write". */ + if (regno < 0) + { + int i; + for (i = 0; + i < gdbarch_num_regs (get_regcache_arch (regcache)); + i++) + { + regcache_invalidate (regcache, i); + } + } + else + { + regcache_invalidate (regcache, regno); + } + + error (_("Process record canceled the operation.")); + } + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + record_registers_change (regcache, regno); + } + record_beneath_to_store_registers (regcache, regno); +} + +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. + In replay mode, we cannot write memory unles we are willing to + invalidate the record/replay log from this point forward. */ + +static LONGEST +record_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte * readbuf, + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) +{ + if (!record_gdb_operation_disable + && (object == TARGET_OBJECT_MEMORY + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + { + if (RECORD_IS_REPLAY) + { + /* Let user choose if he wants to write memory or not. */ + if (!nquery (_("Because GDB is in replay mode, writing to memory will make the execution log unusable from this point onward. Write memory at address 0x%s?"), + paddr_nz (offset))) + { + return -1; + } + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + /* Check record_insn_num */ + record_check_insn_num (0); + + /* Record registers change to list as an instruction. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + if (record_arch_list_add_mem (offset, len)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, + _ + ("Process record: failed to record execution log.")); + } + return -1; + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + { + fprintf_unfiltered (gdb_stdlog, + _ + ("Process record: failed to record execution log.")); + } + return -1; + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + { + record_list_release_first (); + } + else + { + record_insn_num++; + } + } + + return record_beneath_to_xfer_partial (ops, object, annex, readbuf, + writebuf, offset, len); +} + +/* record_insert_breakpoint + record_remove_breakpoint + Behavior is conditional on RECORD_IS_REPLAY. + We will not actually insert or remove breakpoints when replaying, + nor when recording. */ + +static int +record_insert_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_insert_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_remove_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_remove_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_can_execute_reverse (void) +{ + return 1; +} + +static void +init_record_ops (void) +{ + record_ops.to_shortname = "record"; + record_ops.to_longname = "Process record and replay target"; + record_ops.to_doc = + "Log program while executing and replay execution from log."; + record_ops.to_open = record_open; + record_ops.to_close = record_close; + record_ops.to_resume = record_resume; + record_ops.to_wait = record_wait; + record_ops.to_disconnect = record_disconnect; + record_ops.to_detach = record_detach; + record_ops.to_mourn_inferior = record_mourn_inferior; + record_ops.to_kill = record_kill; + record_ops.to_create_inferior = find_default_create_inferior; /* Make record support command "run". */ + record_ops.to_store_registers = record_store_registers; + record_ops.to_xfer_partial = record_xfer_partial; + record_ops.to_insert_breakpoint = record_insert_breakpoint; + record_ops.to_remove_breakpoint = record_remove_breakpoint; + record_ops.to_can_execute_reverse = record_can_execute_reverse; + record_ops.to_stratum = record_stratum; + record_ops.to_magic = OPS_MAGIC; +} + +static void +show_record_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), + value); +} + +/* cmd_record_start -- alias for "target record". */ + +static void +cmd_record_start (char *args, int from_tty) +{ + execute_command ("target record", from_tty); +} + +/* cmd_record_delete -- truncate the record log from the present point + of replay until the end. */ + +static void +cmd_record_delete (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (RECORD_IS_REPLAY) + { + if (!from_tty || query (_("Delete the log from this point forward and begin to record the running message at current PC?"))) + { + record_list_release_next (); + } + } + else + { + printf_unfiltered (_("Already at end of record list.\n")); + } + + } + else + { + printf_unfiltered (_("Process record is not started.\n")); + } +} + +/* cmd_record_stop -- implement the "stoprecord" command. */ + +static void +cmd_record_stop (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (!record_list || !from_tty || query (_("Delete recorded log and stop recording?"))) + { + unpush_target (&record_ops); + } + } + else + { + printf_unfiltered (_("Process record is not started.\n")); + } +} + +/* set_record_insn_max_num -- set upper limit of record log size. */ + +static void +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) +{ + if (record_insn_num > record_insn_max_num && record_insn_max_num) + { + printf_unfiltered (_("Record instructions number is bigger than record instructions max number. Auto delete the first ones?\n")); + + while (record_insn_num > record_insn_max_num) + { + record_list_release_first (); + } + } +} + +/* show_record_insn_number -- print the current index + into the record log (number of insns recorded so far). */ + +static void +show_record_insn_number (char *ignore, int from_tty) +{ + printf_unfiltered (_("Record instruction number is %d.\n"), + record_insn_num); +} + +void +_initialize_record (void) +{ + /* Init record_maskall. */ + if (sigfillset (&record_maskall) == -1) + { + perror_with_name (_("Process record: sigfillset failed")); + } + + /* Init record_first. */ + record_first.prev = NULL; + record_first.next = NULL; + record_first.type = record_end; + record_first.u.need_dasm = 0; + + init_record_ops (); + add_target (&record_ops); + + add_setshow_zinteger_cmd ("record", no_class, &record_debug, + _("Set debugging of record/replay feature."), + _("Show debugging of record/replay feature."), + _ + ("When enabled, debugging output for record/replay feature is displayed."), + NULL, show_record_debug, &setdebuglist, + &showdebuglist); + + add_com ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command.")); + + add_com_alias ("rec", "record", class_obscure, 1); + + /* XXX: I try to use some simple commands such as "disconnect" and + "detach" to support this functions. But these commands all have + other affect to GDB such as call function "no_shared_libraries". + So I add special commands to GDB. */ + add_com ("delrecord", class_obscure, cmd_record_delete, + _("Delete the rest of execution log and start recording it anew.")); + add_com_alias ("dr", "delrecord", class_obscure, 1); + add_com ("stoprecord", class_obscure, cmd_record_stop, + _("Stop the record/replay target.")); + add_com_alias ("sr", "stoprecord", class_obscure, 1); + + /* Record instructions number limit command. */ + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, + &record_stop_at_limit, + _("Set whether record/replay stop when record/replay buffer becomes full."), + _("Show whether record/replay stop when record/replay buffer becomes full."), _("\ +Enable is default value.\n\ +When enabled, if the record/replay buffer becomes full,\n\ +ask user what to do.\n\ +When disabled, if the record/replay buffer becomes full,\n\ +delete it and start new recording."), NULL, NULL, &setlist, &showlist); + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, + &record_insn_max_num, + _("Set record/replay buffer limit."), + _("Show record/replay buffer limit."), _("\ +Set the maximum number of instructions to be stored in the\n\ +record/replay buffer. Zero means unlimited (default 200000)."), + set_record_insn_max_num, + NULL, &setlist, &showlist); + add_info ("record-insn-number", show_record_insn_number, _("\ +Show the current number of instructions in the record/replay buffer.")); +} --- a//dev/null +++ b/record.h @@ -0,0 +1,100 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 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/>. */ + +#ifndef _RECORD_H_ +#define _RECORD_H_ + +#define TARGET_IS_PROCESS_RECORD \ + (current_target.beneath == &record_ops) +#define RECORD_IS_REPLAY \ + (record_list->next || execution_direction == EXEC_REVERSE) + +typedef struct record_reg_s +{ + int num; + gdb_byte *val; +} record_reg_t; + +typedef struct record_mem_s +{ + CORE_ADDR addr; + int len; + gdb_byte *val; +} record_mem_t; + +enum record_type +{ + record_end = 0, + record_reg, + record_mem +}; + +/* This is the core struct of record function. + + An entity of record_t is a record of the value change of a register + ("record_reg") or a part of memory ("record_mem"). And each + instruction must has a record_t ("record_end") that points out this + is the last record_t of this instruction. + + Each record_t is linked to "record_list" by "prev" and "next". + */ +typedef struct record_s +{ + struct record_s *prev; + struct record_s *next; + enum record_type type; + union + { + /* reg */ + record_reg_t reg; + /* mem */ + record_mem_t mem; + /* end */ + int need_dasm; + } u; +} record_t; + +extern int record_debug; +extern record_t *record_list; +extern record_t *record_arch_list_head; +extern record_t *record_arch_list_tail; +extern struct regcache *record_regcache; + +extern struct target_ops record_ops; + +extern int record_arch_list_add_reg (int num); +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); +extern int record_arch_list_add_end (int need_dasm); +extern void record_message (struct gdbarch *gdbarch); +extern struct cleanup * record_gdb_operation_disable_set (void); + +extern void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); +extern ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); +extern void (*record_beneath_to_store_registers) (struct regcache *, int regno); +extern LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, + LONGEST len); +extern int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); +extern int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); + +#endif /* _RECORD_H_ */ ^ permalink raw reply [flat|nested] 38+ messages in thread
* RE: [RFA] Submit process record and replay third time, 3/9 2009-01-13 3:06 ` teawater @ 2009-01-20 18:32 ` Marc Khouzam 2009-01-20 19:47 ` Marc Khouzam 2009-01-21 2:53 ` teawater 0 siblings, 2 replies; 38+ messages in thread From: Marc Khouzam @ 2009-01-20 18:32 UTC (permalink / raw) To: teawater, gdb-patches Hi, again, thanks for the great work on Process Record and Replay. I almost have the Eclipse DSF-GDB frontent for it. However I ran into a small issue where a frontend is unable to turn off Record. I'm not sure if there should be an MI command corresponding to the 'record' command, but if I have to use 'record' directly to turn *off* reverse debugging, I get a prompt "Process record target already running, do you want to delete the old record log?(y or [n])" The prompt is not the problem. The problem is that the default is 'no'. I think that when running in a frontend (at least in eclipse), the default choice automatically gets selected. Therefore, the frontend cannot turn off recording. Could we make the default to be 'yes'? I get the feeling there is a better way to handle this... I just don't know how. Thanks Marc > -----Original Message----- > From: gdb-patches-owner@sourceware.org > [mailto:gdb-patches-owner@sourceware.org] On Behalf Of teawater > Sent: Monday, January 12, 2009 10:06 PM > To: gdb-patches@sourceware.org > Subject: Re: [RFA] Submit process record and replay third time, 3/9 > > Update follow the cvs-head. > > On Thu, Jan 8, 2009 at 13:46, teawater <teawater@gmail.com> wrote: > > > > This patch add the process record and replay target. This > is the core > > part of process record and replay. > > > > 2009-01-08 Hui Zhu <teawater@gmail.com> > > > > Process record and replay target. > > > > * Makefile.in (record.c): New file. > > * record.c, record.h: New file. > > > > 2008-12-28 Michael Snyder <msnyder@vmware.com> > > * Comments, spelling, white space clean-ups. > > > > 2008-12-26 Michael Snyder <msnyder@vmware.com> > > * record.h: Don't export record_not_record. > > * record.c (record_not_record): Rename to in_record_wait. > > (record_not_record_set): Rename to in_record_wait_set. > > (record_not_record_cleanup): Rename to > in_record_wait_cleanup. > > (record_store_registers): Check in_record_wait flag. > > (record_xfer_partial): Ditto. > > > > 2008-10-07 Michael Snyder <msnyder@vmware.com> > > * record.h (record_exec_direction): Delete. > > (RECORD_IS_REPLAY): Consult infrun global direction variable. > > * record.c: (record_wait_cleanups): Use infrun state > variable. > > (record_wait): Ditto. > > (record_get_exec_direction, > record_set_exec_direction): Remove. > > (record_can_execute_reverse): New target method. > > > > 2008-10-06 Michael Snyder <msnyder@vmware.com> > > * record.c (displaced_step_fixup): Remove. > > (record_message_cleanups): Remove displaced step handling. > > (record_message): Remove displaced step handling. > > > > 2008-10-06 Michael Snyder <msnyder@vmware.com> > > * record.c (record_regcache_raw_write_regnum): Remove. > > > > 2008-10-05 Michael Snyder <msnyder@vmware.com> > > * record.c, record.h: Rename execdir to exec_direction. > > > > 2008-10-02 Michael Snyder <msnyder@vmware.com> > > * record.c (record_open): Call target_can_async_p() instead > > of relying on a global variable. > > * record.h (record_linux_async_permitted): Delete. > > > > 2008-09-19 Michael Snyder <msnyder@vmware.com> > > * record.c (trivial): Fix two commas in a comment. > > * record.c (record_wait): On end of record log, return > > TARGET_WAITKIND_NO_HISTORY and let infrun decide what to do. > > > > 2008-09-06 Michael Snyder <msnyder@vmware.com> > > * record.c: Comment and message string cleanup. > > Add some function header comments. > > > > 2008-08-01 Michael Snyder <msnyder@specifix.com> > > * record.c (_initialize_record): Clarify language in help > > strings. > > Fix up comment format (period must be followed by > two spaces). > ^ permalink raw reply [flat|nested] 38+ messages in thread
* RE: [RFA] Submit process record and replay third time, 3/9 2009-01-20 18:32 ` Marc Khouzam @ 2009-01-20 19:47 ` Marc Khouzam 2009-01-21 0:20 ` teawater 2009-01-21 2:53 ` teawater 1 sibling, 1 reply; 38+ messages in thread From: Marc Khouzam @ 2009-01-20 19:47 UTC (permalink / raw) To: Marc Khouzam, teawater, gdb-patches Nothing like a public email to make one realize he is confused :-) I just realized that 'record' is only used to enable recording while 'stoprecord' should be used to turn it off. That works for me. So all is well, sorry about that. Marc > -----Original Message----- > From: gdb-patches-owner@sourceware.org > [mailto:gdb-patches-owner@sourceware.org] On Behalf Of Marc Khouzam > Sent: Tuesday, January 20, 2009 1:31 PM > To: teawater; gdb-patches@sourceware.org > Subject: RE: [RFA] Submit process record and replay third time, 3/9 > > Hi, > > again, thanks for the great work on Process Record and Replay. > I almost have the Eclipse DSF-GDB frontent for it. > However I ran into a small issue where a frontend is unable to turn > off Record. I'm not sure if there should be an MI command > corresponding > to the 'record' command, but if I have to use 'record' directly to > turn *off* reverse debugging, I get a prompt > > "Process record target already running, do you want to delete the old > record log?(y or [n])" > > The prompt is not the problem. The problem is that the > default is 'no'. > I think that when running in a frontend (at least in eclipse), the > default > choice automatically gets selected. Therefore, the frontend > cannot turn > off > recording. > > Could we make the default to be 'yes'? > I get the feeling there is a better way to handle this... I just don't > know how. > > Thanks > > Marc > > > > -----Original Message----- > > From: gdb-patches-owner@sourceware.org > > [mailto:gdb-patches-owner@sourceware.org] On Behalf Of teawater > > Sent: Monday, January 12, 2009 10:06 PM > > To: gdb-patches@sourceware.org > > Subject: Re: [RFA] Submit process record and replay third time, 3/9 > > > > Update follow the cvs-head. > > > > On Thu, Jan 8, 2009 at 13:46, teawater <teawater@gmail.com> wrote: > > > > > > This patch add the process record and replay target. This > > is the core > > > part of process record and replay. > > > > > > 2009-01-08 Hui Zhu <teawater@gmail.com> > > > > > > Process record and replay target. > > > > > > * Makefile.in (record.c): New file. > > > * record.c, record.h: New file. > > > > > > 2008-12-28 Michael Snyder <msnyder@vmware.com> > > > * Comments, spelling, white space clean-ups. > > > > > > 2008-12-26 Michael Snyder <msnyder@vmware.com> > > > * record.h: Don't export record_not_record. > > > * record.c (record_not_record): Rename to in_record_wait. > > > (record_not_record_set): Rename to in_record_wait_set. > > > (record_not_record_cleanup): Rename to > > in_record_wait_cleanup. > > > (record_store_registers): Check in_record_wait flag. > > > (record_xfer_partial): Ditto. > > > > > > 2008-10-07 Michael Snyder <msnyder@vmware.com> > > > * record.h (record_exec_direction): Delete. > > > (RECORD_IS_REPLAY): Consult infrun global > direction variable. > > > * record.c: (record_wait_cleanups): Use infrun state > > variable. > > > (record_wait): Ditto. > > > (record_get_exec_direction, > > record_set_exec_direction): Remove. > > > (record_can_execute_reverse): New target method. > > > > > > 2008-10-06 Michael Snyder <msnyder@vmware.com> > > > * record.c (displaced_step_fixup): Remove. > > > (record_message_cleanups): Remove displaced step handling. > > > (record_message): Remove displaced step handling. > > > > > > 2008-10-06 Michael Snyder <msnyder@vmware.com> > > > * record.c (record_regcache_raw_write_regnum): Remove. > > > > > > 2008-10-05 Michael Snyder <msnyder@vmware.com> > > > * record.c, record.h: Rename execdir to exec_direction. > > > > > > 2008-10-02 Michael Snyder <msnyder@vmware.com> > > > * record.c (record_open): Call target_can_async_p() instead > > > of relying on a global variable. > > > * record.h (record_linux_async_permitted): Delete. > > > > > > 2008-09-19 Michael Snyder <msnyder@vmware.com> > > > * record.c (trivial): Fix two commas in a comment. > > > * record.c (record_wait): On end of record log, return > > > TARGET_WAITKIND_NO_HISTORY and let infrun decide > what to do. > > > > > > 2008-09-06 Michael Snyder <msnyder@vmware.com> > > > * record.c: Comment and message string cleanup. > > > Add some function header comments. > > > > > > 2008-08-01 Michael Snyder <msnyder@specifix.com> > > > * record.c (_initialize_record): Clarify language in help > > > strings. > > > Fix up comment format (period must be followed by > > two spaces). > > > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-20 19:47 ` Marc Khouzam @ 2009-01-21 0:20 ` teawater 0 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-01-21 0:20 UTC (permalink / raw) To: Marc Khouzam; +Cc: gdb-patches So. Your question in prev mail is OK? On Wed, Jan 21, 2009 at 03:47, Marc Khouzam <marc.khouzam@ericsson.com> wrote: > Nothing like a public email to make one realize he is confused :-) > > I just realized that 'record' is only used to enable recording while > 'stoprecord' should be used to turn it off. That works for me. > > So all is well, sorry about that. > > Marc > >> -----Original Message----- >> From: gdb-patches-owner@sourceware.org >> [mailto:gdb-patches-owner@sourceware.org] On Behalf Of Marc Khouzam >> Sent: Tuesday, January 20, 2009 1:31 PM >> To: teawater; gdb-patches@sourceware.org >> Subject: RE: [RFA] Submit process record and replay third time, 3/9 >> >> Hi, >> >> again, thanks for the great work on Process Record and Replay. >> I almost have the Eclipse DSF-GDB frontent for it. >> However I ran into a small issue where a frontend is unable to turn >> off Record. I'm not sure if there should be an MI command >> corresponding >> to the 'record' command, but if I have to use 'record' directly to >> turn *off* reverse debugging, I get a prompt >> >> "Process record target already running, do you want to delete the old >> record log?(y or [n])" >> >> The prompt is not the problem. The problem is that the >> default is 'no'. >> I think that when running in a frontend (at least in eclipse), the >> default >> choice automatically gets selected. Therefore, the frontend >> cannot turn >> off >> recording. >> >> Could we make the default to be 'yes'? >> I get the feeling there is a better way to handle this... I just don't >> know how. >> >> Thanks >> >> Marc >> >> >> > -----Original Message----- >> > From: gdb-patches-owner@sourceware.org >> > [mailto:gdb-patches-owner@sourceware.org] On Behalf Of teawater >> > Sent: Monday, January 12, 2009 10:06 PM >> > To: gdb-patches@sourceware.org >> > Subject: Re: [RFA] Submit process record and replay third time, 3/9 >> > >> > Update follow the cvs-head. >> > >> > On Thu, Jan 8, 2009 at 13:46, teawater <teawater@gmail.com> wrote: >> > > >> > > This patch add the process record and replay target. This >> > is the core >> > > part of process record and replay. >> > > >> > > 2009-01-08 Hui Zhu <teawater@gmail.com> >> > > >> > > Process record and replay target. >> > > >> > > * Makefile.in (record.c): New file. >> > > * record.c, record.h: New file. >> > > >> > > 2008-12-28 Michael Snyder <msnyder@vmware.com> >> > > * Comments, spelling, white space clean-ups. >> > > >> > > 2008-12-26 Michael Snyder <msnyder@vmware.com> >> > > * record.h: Don't export record_not_record. >> > > * record.c (record_not_record): Rename to in_record_wait. >> > > (record_not_record_set): Rename to in_record_wait_set. >> > > (record_not_record_cleanup): Rename to >> > in_record_wait_cleanup. >> > > (record_store_registers): Check in_record_wait flag. >> > > (record_xfer_partial): Ditto. >> > > >> > > 2008-10-07 Michael Snyder <msnyder@vmware.com> >> > > * record.h (record_exec_direction): Delete. >> > > (RECORD_IS_REPLAY): Consult infrun global >> direction variable. >> > > * record.c: (record_wait_cleanups): Use infrun state >> > variable. >> > > (record_wait): Ditto. >> > > (record_get_exec_direction, >> > record_set_exec_direction): Remove. >> > > (record_can_execute_reverse): New target method. >> > > >> > > 2008-10-06 Michael Snyder <msnyder@vmware.com> >> > > * record.c (displaced_step_fixup): Remove. >> > > (record_message_cleanups): Remove displaced step handling. >> > > (record_message): Remove displaced step handling. >> > > >> > > 2008-10-06 Michael Snyder <msnyder@vmware.com> >> > > * record.c (record_regcache_raw_write_regnum): Remove. >> > > >> > > 2008-10-05 Michael Snyder <msnyder@vmware.com> >> > > * record.c, record.h: Rename execdir to exec_direction. >> > > >> > > 2008-10-02 Michael Snyder <msnyder@vmware.com> >> > > * record.c (record_open): Call target_can_async_p() instead >> > > of relying on a global variable. >> > > * record.h (record_linux_async_permitted): Delete. >> > > >> > > 2008-09-19 Michael Snyder <msnyder@vmware.com> >> > > * record.c (trivial): Fix two commas in a comment. >> > > * record.c (record_wait): On end of record log, return >> > > TARGET_WAITKIND_NO_HISTORY and let infrun decide >> what to do. >> > > >> > > 2008-09-06 Michael Snyder <msnyder@vmware.com> >> > > * record.c: Comment and message string cleanup. >> > > Add some function header comments. >> > > >> > > 2008-08-01 Michael Snyder <msnyder@specifix.com> >> > > * record.c (_initialize_record): Clarify language in help >> > > strings. >> > > Fix up comment format (period must be followed by >> > two spaces). >> > >> > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-20 18:32 ` Marc Khouzam 2009-01-20 19:47 ` Marc Khouzam @ 2009-01-21 2:53 ` teawater 1 sibling, 0 replies; 38+ messages in thread From: teawater @ 2009-01-21 2:53 UTC (permalink / raw) To: Marc Khouzam; +Cc: gdb-patches Sorry for my prev mail. Cause I always read the small mail first. :) stoprecord is to stop process record. And feel free to mail me if you still have some problem with it. Thanks, Hui On Wed, Jan 21, 2009 at 02:31, Marc Khouzam <marc.khouzam@ericsson.com> wrote: > Hi, > > again, thanks for the great work on Process Record and Replay. > I almost have the Eclipse DSF-GDB frontent for it. > However I ran into a small issue where a frontend is unable to turn > off Record. I'm not sure if there should be an MI command corresponding > to the 'record' command, but if I have to use 'record' directly to > turn *off* reverse debugging, I get a prompt > > "Process record target already running, do you want to delete the old > record log?(y or [n])" > > The prompt is not the problem. The problem is that the default is 'no'. > I think that when running in a frontend (at least in eclipse), the > default > choice automatically gets selected. Therefore, the frontend cannot turn > off > recording. > > Could we make the default to be 'yes'? > I get the feeling there is a better way to handle this... I just don't > know how. > > Thanks > > Marc > > >> -----Original Message----- >> From: gdb-patches-owner@sourceware.org >> [mailto:gdb-patches-owner@sourceware.org] On Behalf Of teawater >> Sent: Monday, January 12, 2009 10:06 PM >> To: gdb-patches@sourceware.org >> Subject: Re: [RFA] Submit process record and replay third time, 3/9 >> >> Update follow the cvs-head. >> >> On Thu, Jan 8, 2009 at 13:46, teawater <teawater@gmail.com> wrote: >> > >> > This patch add the process record and replay target. This >> is the core >> > part of process record and replay. >> > >> > 2009-01-08 Hui Zhu <teawater@gmail.com> >> > >> > Process record and replay target. >> > >> > * Makefile.in (record.c): New file. >> > * record.c, record.h: New file. >> > >> > 2008-12-28 Michael Snyder <msnyder@vmware.com> >> > * Comments, spelling, white space clean-ups. >> > >> > 2008-12-26 Michael Snyder <msnyder@vmware.com> >> > * record.h: Don't export record_not_record. >> > * record.c (record_not_record): Rename to in_record_wait. >> > (record_not_record_set): Rename to in_record_wait_set. >> > (record_not_record_cleanup): Rename to >> in_record_wait_cleanup. >> > (record_store_registers): Check in_record_wait flag. >> > (record_xfer_partial): Ditto. >> > >> > 2008-10-07 Michael Snyder <msnyder@vmware.com> >> > * record.h (record_exec_direction): Delete. >> > (RECORD_IS_REPLAY): Consult infrun global direction variable. >> > * record.c: (record_wait_cleanups): Use infrun state >> variable. >> > (record_wait): Ditto. >> > (record_get_exec_direction, >> record_set_exec_direction): Remove. >> > (record_can_execute_reverse): New target method. >> > >> > 2008-10-06 Michael Snyder <msnyder@vmware.com> >> > * record.c (displaced_step_fixup): Remove. >> > (record_message_cleanups): Remove displaced step handling. >> > (record_message): Remove displaced step handling. >> > >> > 2008-10-06 Michael Snyder <msnyder@vmware.com> >> > * record.c (record_regcache_raw_write_regnum): Remove. >> > >> > 2008-10-05 Michael Snyder <msnyder@vmware.com> >> > * record.c, record.h: Rename execdir to exec_direction. >> > >> > 2008-10-02 Michael Snyder <msnyder@vmware.com> >> > * record.c (record_open): Call target_can_async_p() instead >> > of relying on a global variable. >> > * record.h (record_linux_async_permitted): Delete. >> > >> > 2008-09-19 Michael Snyder <msnyder@vmware.com> >> > * record.c (trivial): Fix two commas in a comment. >> > * record.c (record_wait): On end of record log, return >> > TARGET_WAITKIND_NO_HISTORY and let infrun decide what to do. >> > >> > 2008-09-06 Michael Snyder <msnyder@vmware.com> >> > * record.c: Comment and message string cleanup. >> > Add some function header comments. >> > >> > 2008-08-01 Michael Snyder <msnyder@specifix.com> >> > * record.c (_initialize_record): Clarify language in help >> > strings. >> > Fix up comment format (period must be followed by >> two spaces). >> > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-08 5:46 [RFA] Submit process record and replay third time, 3/9 teawater 2009-01-13 3:06 ` teawater @ 2009-01-22 13:23 ` Pedro Alves 2009-01-22 15:23 ` teawater ` (2 more replies) 1 sibling, 3 replies; 38+ messages in thread From: Pedro Alves @ 2009-01-22 13:23 UTC (permalink / raw) To: gdb-patches; +Cc: teawater Hi Hui, I've just skimmed through this patch, comments below. On Thursday 08 January 2009 05:46:17, teawater wrote: > This patch add the process record and replay target. This is the core > part of process record and replay. You still haven't addressed several past comments. :-( 1) Nit-picky nature, but I've warned you about it several months ago, so I'm escalating it. :-) You have several formatting things you need to clean up. Please change instances of: if (foo) { bar (); } To: if (foo) bar (); Please make sure you don't have any line exceeding 80 columns. Please remove redundant ()'s, like in `return (0)'; 2) This bit, +/* The real beneath function pointers. */ +void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); +ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); +void (*record_beneath_to_store_registers) (struct regcache *, int regno); +LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, LONGEST len); +int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); +int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); And the corresponding bit in target.c that sets these function pointers, and the RECORD_IS_REPLAY and TARGET_IS_PROCESS_RECORD macros, add coupling between the record target, and the core of gdb, that sounds unnecessary if we're adding record as a target stratum. I'd really like to see those function pointers go away, and mentioned adding new target vector entries for the properties of record target you want checked in common code. I've suggested how before, did you try it? 2.1) Related to coupling as well. You've added record.c to the list of files that are built on all hosts, but I don't think that record.c is currently buildable on all hosts. E.g., you're using sigaction unconditionally. I didn't spot any call to a function defined in a *-nat.c file in this patch, but if you have any, you'll need to either remove/rewrite it (ideal, I expect), or build record.c on native linux hosts only. 3) This is a new one: I'd prefer we don't add calls to normal_stop outside core inferior control code. There's only one left in go32-nat.c, and that has been on my list to eliminate. -- Pedro Alves ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-22 13:23 ` Pedro Alves @ 2009-01-22 15:23 ` teawater 2009-01-22 22:33 ` Pedro Alves 2009-01-23 14:56 ` teawater 2009-02-02 9:05 ` teawater 2 siblings, 1 reply; 38+ messages in thread From: teawater @ 2009-01-22 15:23 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches Thanks Pedro, On Thu, Jan 22, 2009 at 21:24, Pedro Alves <pedro@codesourcery.com> wrote: > Hi Hui, I've just skimmed through this patch, comments below. > > On Thursday 08 January 2009 05:46:17, teawater wrote: >> This patch add the process record and replay target. This is the core >> part of process record and replay. > > You still haven't addressed several past comments. :-( Sorry for it. It must be my gmail lost them. :-( I will try my best with them. > > 1) Nit-picky nature, but I've warned you about it several months ago, so > I'm escalating it. :-) > > You have several formatting things you need to clean up. > > Please change instances of: > > if (foo) > { > bar (); > } > > To: > > if (foo) > bar (); > > Please make sure you don't have any line exceeding 80 columns. > > Please remove redundant ()'s, like in `return (0)'; I will fix it. > > 2) This bit, > > +/* The real beneath function pointers. */ > +void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); > +ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); > +void (*record_beneath_to_store_registers) (struct regcache *, int regno); > +LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, > + enum target_object object, > + const char *annex, > + gdb_byte * readbuf, > + const gdb_byte * writebuf, > + ULONGEST offset, LONGEST len); > +int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); > +int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); > > And the corresponding bit in target.c that sets these function pointers, and > the RECORD_IS_REPLAY and TARGET_IS_PROCESS_RECORD macros, add coupling > between the record target, and the core of gdb, that sounds unnecessary if > we're adding record as a target stratum. I'd really like to see those function > pointers go away, and mentioned adding new target vector entries for the properties > of record target you want checked in common code. I've suggested how before, did > you try it? This part in there because: Process record and replay target need handler the debug behavior such as resume, wait and insert breakpoint. And GDB will call the target function that "to_stratum" is biggest one (If this one is NULL, GDB will call the function that lower than this one). So add a "record_stratum" to enum strata and set "to_stratum" of process record to "record_stratum". Then, when GDB call debug function, it will call the function in process record target. When GDB in record mode, we need to call the really debug function in low strata target because process record and replay target need call this function to control the inferior. Struct target_ops already has a pointer "beneath" point to low strata target, but process record and replay target doesn't use it. Because if low strata target doesn't set some function pointers, process record and replay target will need to call the function pointers of the target that is low strata target of this target. If this target doesn't set them too, it will need to call anothers. So use "beneath" is not a good choice and "multi-thread" target that need function pointers of low strata target doesn't use "beneath" too. The process record and replay target has 6 function pointers record_beneath_to_resume, record_beneath_to_wait, record_beneath_to_prepare_to_store, record_beneath_to_xfer_partial, record_beneath_to_insert_breakpoint and record_beneath_to_remove_breakpoint. They are set in function "update_current_target". They are always point to the function of low strata target. > > 2.1) Related to coupling as well. You've added record.c to the list of files that are > built on all hosts, but I don't think that record.c is currently buildable on all > hosts. E.g., you're using sigaction unconditionally. I didn't spot any call to > a function defined in a *-nat.c file in this patch, but if you have any, you'll need > to either remove/rewrite it (ideal, I expect), or build record.c on native > linux hosts only. OK. I will put it close to linux-record.c. Wish it can be back in the future. > > 3) This is a new one: I'd prefer we don't add calls to > normal_stop outside core inferior control code. There's only one left in go32-nat.c, > and that has been on my list to eliminate. > OK. I will deal with it. Thanks, Hui ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-22 15:23 ` teawater @ 2009-01-22 22:33 ` Pedro Alves 2009-01-22 22:36 ` Pedro Alves 0 siblings, 1 reply; 38+ messages in thread From: Pedro Alves @ 2009-01-22 22:33 UTC (permalink / raw) To: gdb-patches; +Cc: teawater On Thursday 22 January 2009 15:23:48, teawater wrote: > > 2.1) Related to coupling as well. You've added record.c to the list of files that are > > built on all hosts, but I don't think that record.c is currently buildable on all > > hosts. E.g., you're using sigaction unconditionally. I didn't spot any call to > > a function defined in a *-nat.c file in this patch, but if you have any, you'll need > > to either remove/rewrite it (ideal, I expect), or build record.c on native > > linux hosts only. > > OK. I will put it close to linux-record.c. Wish it can be back in the future. I think you're confused. You've added linux-record.c in configure.tgt. Those files will also be built in a cross configuration --- say, if you build a Windows hosted, linux targeted gdb. Files that are only to be built when you're building a native debugger are added to NATDEPFILES in the various .mt files under src/gdb/config/. Now, if you added record.o to the relevant linux NATDEPFILES, you'd still be breaking all other hosts, because you have references to functions and variables defined in record.c throughout gdb's core. Since other hosts wouldn't include the record.o object, they'd fail to link. Here's what I propose you try as first step: remove all references to record.o from Makefile.in and configure.tgt. Try building. You'll get link errors. Now, come up with interfaces between the core and record.c that would preserve all functionality if record.o isn't included in the link; but that will still link a gdb executable, if record.o isn't linked in. -- Pedro Alves ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-22 22:33 ` Pedro Alves @ 2009-01-22 22:36 ` Pedro Alves 2009-01-23 0:00 ` teawater 0 siblings, 1 reply; 38+ messages in thread From: Pedro Alves @ 2009-01-22 22:36 UTC (permalink / raw) To: gdb-patches; +Cc: teawater On Thursday 22 January 2009 22:34:25, Pedro Alves write: > Here's what I propose you try as first step: remove all references to record.o > from Makefile.in and configure.tgt. Try building. You'll get link errors. Now, > come up with interfaces between the core and record.c that would preserve all > functionality if record.o isn't included in the link; but that will still link > a gdb executable, if record.o isn't linked in. Sigh, I said "isn't" when I mean "is". Let me try again, Here's what I propose you try as first step: remove all references to record.o from Makefile.in and configure.tgt. Try building. You'll get link errors. Now, come up with interfaces between the core and record.c that would preserve all functionality if record.o *is* included in the link; but that will still link a gdb executable, if record.o *isn't* linked in. -- Pedro Alves ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-22 22:36 ` Pedro Alves @ 2009-01-23 0:00 ` teawater 2009-01-23 6:58 ` teawater 0 siblings, 1 reply; 38+ messages in thread From: teawater @ 2009-01-23 0:00 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches I think my prev mail is not right. linux-record.c is not like record.c. It's for the tgt part and it doesn't have some os-special code. I think I will try to put it close to linux-nat.c. BTW I really want record.c to be a os-nospecial code. Do you have some idea with it. I think there just 2 sigaction in there, maybe I can make it not os-special. And for this record_beneath_to_xxx function pointer, do you agree with I said? Thanks, Hui On Fri, Jan 23, 2009 at 06:36, Pedro Alves <pedro@codesourcery.com> wrote: > On Thursday 22 January 2009 22:34:25, Pedro Alves write: >> Here's what I propose you try as first step: remove all references to record.o >> from Makefile.in and configure.tgt. Try building. You'll get link errors. Now, >> come up with interfaces between the core and record.c that would preserve all >> functionality if record.o isn't included in the link; but that will still link >> a gdb executable, if record.o isn't linked in. > > Sigh, I said "isn't" when I mean "is". Let me try again, > > Here's what I propose you try as first step: remove all references to record.o > from Makefile.in and configure.tgt. Try building. You'll get link errors. Now, > come up with interfaces between the core and record.c that would preserve all > functionality if record.o *is* included in the link; but that will still link > a gdb executable, if record.o *isn't* linked in. > > -- > Pedro Alves > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-23 0:00 ` teawater @ 2009-01-23 6:58 ` teawater 0 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-01-23 6:58 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 1711 bytes --] Hi Pedro, I found that remote.c use signal so I make a patch change sigaction to signal. Do you think I can keep record.c in all place with this patch? Thanks, Hui On Fri, Jan 23, 2009 at 08:00, teawater <teawater@gmail.com> wrote: > I think my prev mail is not right. > > linux-record.c is not like record.c. It's for the tgt part and it > doesn't have some os-special code. > > I think I will try to put it close to linux-nat.c. > > BTW I really want record.c to be a os-nospecial code. Do you have some > idea with it. I think there just 2 sigaction in there, maybe I can > make it not os-special. > > > And for this record_beneath_to_xxx function pointer, do you agree with I said? > > > Thanks, > Hui > > On Fri, Jan 23, 2009 at 06:36, Pedro Alves <pedro@codesourcery.com> wrote: >> On Thursday 22 January 2009 22:34:25, Pedro Alves write: >>> Here's what I propose you try as first step: remove all references to record.o >>> from Makefile.in and configure.tgt. Try building. You'll get link errors. Now, >>> come up with interfaces between the core and record.c that would preserve all >>> functionality if record.o isn't included in the link; but that will still link >>> a gdb executable, if record.o isn't linked in. >> >> Sigh, I said "isn't" when I mean "is". Let me try again, >> >> Here's what I propose you try as first step: remove all references to record.o >> from Makefile.in and configure.tgt. Try building. You'll get link errors. Now, >> come up with interfaces between the core and record.c that would preserve all >> functionality if record.o *is* included in the link; but that will still link >> a gdb executable, if record.o *isn't* linked in. >> >> -- >> Pedro Alves >> > [-- Attachment #2: record-target-remove-sigaction.txt --] [-- Type: text/plain, Size: 1342 bytes --] Index: gdb/record.c =================================================================== --- gdb.orig/record.c 2009-01-23 10:48:23.000000000 +0800 +++ gdb/record.c 2009-01-23 15:04:11.000000000 +0800 @@ -23,6 +23,7 @@ #include "regcache.h" #include "inferior.h" #include "gdbthread.h" +#include "event-top.h" #include "record.h" #include <signal.h> @@ -621,7 +622,6 @@ } else { - struct sigaction act, old_act; int need_dasm = 0; struct regcache *regcache = get_current_regcache (); int continue_flag = 1; @@ -656,13 +656,7 @@ } record_get_sig = 0; - act.sa_handler = record_sig_handler; - act.sa_mask = record_maskall; - act.sa_flags = SA_RESTART; - if (sigaction (SIGINT, &act, &old_act)) - { - perror_with_name (_("Process record: sigaction failed")); - } + signal (SIGINT, record_sig_handler); /* If GDB is in terminal_inferior mode, it will not get the signal. And in GDB replay mode, GDB doesn't need to be in terminal_inferior mode, because inferior will not executed. @@ -831,10 +825,7 @@ } while (continue_flag); - if (sigaction (SIGALRM, &old_act, NULL)) - { - perror_with_name (_("Process record: sigaction failed")); - } + signal (SIGINT, handle_sigint); replay_out: if (record_get_sig) ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-22 13:23 ` Pedro Alves 2009-01-22 15:23 ` teawater @ 2009-01-23 14:56 ` teawater 2009-01-23 15:34 ` Pedro Alves 2009-02-02 9:05 ` teawater 2 siblings, 1 reply; 38+ messages in thread From: teawater @ 2009-01-23 14:56 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches Hi Pedro, Do you think I need change: if (record_debug > 1) { fprintf_unfiltered (gdb_stdlog, "Process record: record_arch_list_add 0x%s.\n", paddr_nz ((CORE_ADDR)rec)); } to: if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: record_arch_list_add 0x%s.\n", paddr_nz ((CORE_ADDR)rec)); Thanks, Hui On Thu, Jan 22, 2009 at 21:24, Pedro Alves <pedro@codesourcery.com> wrote: > Hi Hui, I've just skimmed through this patch, comments below. > > On Thursday 08 January 2009 05:46:17, teawater wrote: >> This patch add the process record and replay target. This is the core >> part of process record and replay. > > You still haven't addressed several past comments. :-( > > 1) Nit-picky nature, but I've warned you about it several months ago, so > I'm escalating it. :-) > > You have several formatting things you need to clean up. > > Please change instances of: > > if (foo) > { > bar (); > } > > To: > > if (foo) > bar (); > > Please make sure you don't have any line exceeding 80 columns. > > Please remove redundant ()'s, like in `return (0)'; > > 2) This bit, > > +/* The real beneath function pointers. */ > +void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); > +ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); > +void (*record_beneath_to_store_registers) (struct regcache *, int regno); > +LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, > + enum target_object object, > + const char *annex, > + gdb_byte * readbuf, > + const gdb_byte * writebuf, > + ULONGEST offset, LONGEST len); > +int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); > +int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); > > And the corresponding bit in target.c that sets these function pointers, and > the RECORD_IS_REPLAY and TARGET_IS_PROCESS_RECORD macros, add coupling > between the record target, and the core of gdb, that sounds unnecessary if > we're adding record as a target stratum. I'd really like to see those function > pointers go away, and mentioned adding new target vector entries for the properties > of record target you want checked in common code. I've suggested how before, did > you try it? > > 2.1) Related to coupling as well. You've added record.c to the list of files that are > built on all hosts, but I don't think that record.c is currently buildable on all > hosts. E.g., you're using sigaction unconditionally. I didn't spot any call to > a function defined in a *-nat.c file in this patch, but if you have any, you'll need > to either remove/rewrite it (ideal, I expect), or build record.c on native > linux hosts only. > > 3) This is a new one: I'd prefer we don't add calls to > normal_stop outside core inferior control code. There's only one left in go32-nat.c, > and that has been on my list to eliminate. > > -- > Pedro Alves > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-23 14:56 ` teawater @ 2009-01-23 15:34 ` Pedro Alves 2009-01-23 15:55 ` teawater 0 siblings, 1 reply; 38+ messages in thread From: Pedro Alves @ 2009-01-23 15:34 UTC (permalink / raw) To: gdb-patches; +Cc: teawater On Friday 23 January 2009 14:56:03, teawater wrote: > Do you think I need change: > if (record_debug > 1) > { > fprintf_unfiltered (gdb_stdlog, > "Process record: record_arch_list_add 0x%s.\n", > paddr_nz ((CORE_ADDR)rec)); > } > to: > if (record_debug > 1) > fprintf_unfiltered (gdb_stdlog, > "Process record: record_arch_list_add 0x%s.\n", > paddr_nz ((CORE_ADDR)rec)); Yes, thank you, but please be sure to keep correct tab/spacing. But..., I just noticed that ``rec'' is a pointer to memory in GDB. Don't ever cast those to CORE_ADDR, which is *target* address. If you think these debug messages are important to keep, then please change this and all similar cases to use host_address_to_string instead of paddr*. -- Pedro Alves ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-23 15:34 ` Pedro Alves @ 2009-01-23 15:55 ` teawater 0 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-01-23 15:55 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches I see. Thanks Pedro. Hui On Fri, Jan 23, 2009 at 23:35, Pedro Alves <pedro@codesourcery.com> wrote: > On Friday 23 January 2009 14:56:03, teawater wrote: > >> Do you think I need change: >> if (record_debug > 1) >> { >> fprintf_unfiltered (gdb_stdlog, >> "Process record: record_arch_list_add 0x%s.\n", >> paddr_nz ((CORE_ADDR)rec)); >> } >> to: >> if (record_debug > 1) >> fprintf_unfiltered (gdb_stdlog, >> "Process record: record_arch_list_add 0x%s.\n", >> paddr_nz ((CORE_ADDR)rec)); > > Yes, thank you, but please be sure to keep correct tab/spacing. > > But..., I just noticed that ``rec'' is a pointer to memory in GDB. > Don't ever cast those to CORE_ADDR, which is *target* address. > > If you think these debug messages are important to keep, then please > change this and all similar cases to use host_address_to_string > instead of paddr*. > > -- > Pedro Alves > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-01-22 13:23 ` Pedro Alves 2009-01-22 15:23 ` teawater 2009-01-23 14:56 ` teawater @ 2009-02-02 9:05 ` teawater 2009-02-08 13:03 ` teawater 2 siblings, 1 reply; 38+ messages in thread From: teawater @ 2009-02-02 9:05 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 3247 bytes --] Hi Pedro, According to your mail, I make a new patch. Could you please help me review it? 1. Change all if (foo) { bar (); } to if (foo) bar (); 2. Change some paddr_nz to host_address_to_string. 3. Change sigaction to signal like remote.c to make it not os special. 4. Remove normal_stop from record.c 5. Make all files don't have any line exceeding 80 columns. Do you think this patch is OK? Thanks, Hui On Thu, Jan 22, 2009 at 21:24, Pedro Alves <pedro@codesourcery.com> wrote: > Hi Hui, I've just skimmed through this patch, comments below. > > On Thursday 08 January 2009 05:46:17, teawater wrote: >> This patch add the process record and replay target. This is the core >> part of process record and replay. > > You still haven't addressed several past comments. :-( > > 1) Nit-picky nature, but I've warned you about it several months ago, so > I'm escalating it. :-) > > You have several formatting things you need to clean up. > > Please change instances of: > > if (foo) > { > bar (); > } > > To: > > if (foo) > bar (); > > Please make sure you don't have any line exceeding 80 columns. > > Please remove redundant ()'s, like in `return (0)'; > > 2) This bit, > > +/* The real beneath function pointers. */ > +void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); > +ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); > +void (*record_beneath_to_store_registers) (struct regcache *, int regno); > +LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, > + enum target_object object, > + const char *annex, > + gdb_byte * readbuf, > + const gdb_byte * writebuf, > + ULONGEST offset, LONGEST len); > +int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); > +int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); > > And the corresponding bit in target.c that sets these function pointers, and > the RECORD_IS_REPLAY and TARGET_IS_PROCESS_RECORD macros, add coupling > between the record target, and the core of gdb, that sounds unnecessary if > we're adding record as a target stratum. I'd really like to see those function > pointers go away, and mentioned adding new target vector entries for the properties > of record target you want checked in common code. I've suggested how before, did > you try it? > > 2.1) Related to coupling as well. You've added record.c to the list of files that are > built on all hosts, but I don't think that record.c is currently buildable on all > hosts. E.g., you're using sigaction unconditionally. I didn't spot any call to > a function defined in a *-nat.c file in this patch, but if you have any, you'll need > to either remove/rewrite it (ideal, I expect), or build record.c on native > linux hosts only. > > 3) This is a new one: I'd prefer we don't add calls to > normal_stop outside core inferior control code. There's only one left in go32-nat.c, > and that has been on my list to eliminate. > > -- > Pedro Alves > [-- Attachment #2: 3-record_target.txt --] [-- Type: text/plain, Size: 37864 bytes --] --- Makefile.in | 4 record.c | 1217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ record.h | 101 ++++ 3 files changed, 1320 insertions(+), 2 deletions(-) --- a/Makefile.in +++ b/Makefile.in @@ -658,7 +658,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr valarith.c valops.c valprint.c value.c varobj.c vec.c \ wrapper.c \ xml-tdesc.c xml-support.c \ - inferior.c + inferior.c record.c LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c @@ -809,7 +809,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ solib.o solib-null.o \ prologue-value.o memory-map.o xml-support.o \ target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ - inferior.o osdata.o + inferior.o osdata.o record.o TSOBS = inflow.o --- /dev/null +++ b/record.c @@ -0,0 +1,1217 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "target.h" +#include "gdbcmd.h" +#include "regcache.h" +#include "inferior.h" +#include "gdbthread.h" +#include "event-top.h" +#include "annotate.h" +#include "observer.h" +#include "record.h" + +#include <signal.h> + +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 + +int record_debug = 0; + +record_t record_first; +record_t *record_list = &record_first; +record_t *record_arch_list_head = NULL; +record_t *record_arch_list_tail = NULL; +struct regcache *record_regcache = NULL; + +/* 1 ask user. 0 auto delete the last record_t. */ +static int record_stop_at_limit = 1; +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; +static int record_insn_num = 0; + +struct target_ops record_ops; +static int record_resume_step = 0; +static enum target_signal record_resume_siggnal; +static int record_get_sig = 0; +static sigset_t record_maskall; +static int record_gdb_operation_disable = 0; +int record_will_store_registers = 0; + +extern struct bp_location *bp_location_chain; + +/* The real beneath function pointers. */ +void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); +ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); +void (*record_beneath_to_store_registers) (struct regcache *, int regno); +LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, LONGEST len); +int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); +int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); + +static void +record_list_release (record_t * rec) +{ + record_t *tmp; + + if (!rec) + return; + + while (rec->next) + { + rec = rec->next; + } + + while (rec->prev) + { + tmp = rec; + rec = rec->prev; + if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + } + + if (rec != &record_first) + xfree (rec); +} + +static void +record_list_release_next (void) +{ + record_t *rec = record_list; + record_t *tmp = rec->next; + rec->next = NULL; + while (tmp) + { + rec = tmp->next; + if (tmp->type == record_reg) + record_insn_num--; + else if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + tmp = rec; + } +} + +static void +record_list_release_first (void) +{ + record_t *tmp = NULL; + enum record_type type; + + if (!record_first.next) + return; + + while (1) + { + type = record_first.next->type; + + if (type == record_reg) + xfree (record_first.next->u.reg.val); + else if (type == record_mem) + xfree (record_first.next->u.mem.val); + tmp = record_first.next; + record_first.next = tmp->next; + xfree (tmp); + + if (!record_first.next) + { + gdb_assert (record_insn_num == 1); + break; + } + + record_first.next->prev = &record_first; + + if (type == record_end) + break; + } + + record_insn_num--; +} + +/* Add a record_t to record_arch_list. */ +static void +record_arch_list_add (record_t * rec) +{ + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_arch_list_add %s.\n", + host_address_to_string (rec)); + + if (record_arch_list_tail) + { + record_arch_list_tail->next = rec; + rec->prev = record_arch_list_tail; + record_arch_list_tail = rec; + } + else + { + record_arch_list_head = rec; + record_arch_list_tail = rec; + } +} + +/* Record the value of a register ("num") to record_arch_list. */ +int +record_arch_list_add_reg (int num) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add register num = %d to " + "record list.\n", + num); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + rec->u.reg.num = num; + + regcache_raw_read (record_regcache, num, rec->u.reg.val); + + record_arch_list_add (rec); + + return 0; +} + +/* Record the value of a region of memory whose address is "addr" and + length is "len" to record_arch_list. */ + +int +record_arch_list_add_mem (CORE_ADDR addr, int len) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add mem addr = 0x%s len = %d to " + "record list.\n", + paddr_nz (addr), len); + + if (!addr) + return 0; + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.mem.val = (gdb_byte *) xmalloc (len); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + rec->u.mem.addr = addr; + rec->u.mem.len = len; + + if (target_read_memory (addr, rec->u.mem.val, len)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: error reading memory at " + "addr = 0x%s len = %d.\n", + paddr_nz (addr), len); + xfree (rec->u.mem.val); + xfree (rec); + return -1; + } + + record_arch_list_add (rec); + + return 0; +} + +/* Add a record_end type record_t to record_arch_list. */ +int +record_arch_list_add_end (int need_dasm) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add end need_dasm = %d to " + "arch list.\n", + need_dasm); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + + rec->u.need_dasm = need_dasm; + + record_arch_list_add (rec); + + return 0; +} + +static void +record_check_insn_num (int set_terminal) +{ + if (record_insn_max_num) + { + gdb_assert (record_insn_num <= record_insn_max_num); + if (record_insn_num == record_insn_max_num) + { + /* Ask user what to do. */ + if (record_stop_at_limit) + { + int q; + if (set_terminal) + target_terminal_ours (); + q = yquery (_("Do you want to auto delete previous execution " + "log entries when record/replay buffer becomes " + "full (record-stop-at-limit)?")); + if (set_terminal) + target_terminal_inferior (); + if (q) + record_stop_at_limit = 0; + else + error (_("Process record: inferior program stopped.")); + } + } + } +} + +static void +record_normal_stop (void) +{ + finish_thread_state (minus_one_ptid); + + if (!breakpoints_always_inserted_mode () && target_has_execution) + remove_breakpoints (); + + target_terminal_ours (); + + if (target_has_stack && !stop_stack_dummy) + set_current_sal_from_frame (get_current_frame (), 1); + + select_frame (get_current_frame ()); + + annotate_stopped (); + if (!suppress_stop_observer) + { + if (!ptid_equal (inferior_ptid, null_ptid)) + observer_notify_normal_stop (inferior_thread ()->stop_bpstat); + else + observer_notify_normal_stop (NULL); + } +} + +/* 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_list_release (record_arch_list_tail); + set_executing (inferior_ptid, 0); + record_normal_stop (); +} + +void +record_message (struct gdbarch *gdbarch) +{ + int ret; + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + /* Check record_insn_num. */ + record_check_insn_num (1); + + record_regcache = get_current_regcache (); + + ret = gdbarch_process_record (gdbarch, + regcache_read_pc (record_regcache)); + if (ret > 0) + error (_("Process record: inferior program stopped.")); + if (ret < 0) + error (_("Process record: failed to record execution log.")); + + discard_cleanups (old_cleanups); + + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +/* Things to clean up if we error or QUIT out of function that set + record_gdb_operation_disable (ie. command that caused target_wait to be called). */ +static void +record_gdb_operation_disable_cleanups (void *ignore) +{ + record_gdb_operation_disable = 0; +} + +struct cleanup * +record_gdb_operation_disable_set (void) +{ + struct cleanup *old_cleanups = make_cleanup (record_gdb_operation_disable_cleanups, 0); + record_gdb_operation_disable = 1; + + return old_cleanups; +} + +static void +record_open (char *name, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + + /* check exec */ + if (!target_has_execution) + error (_("Process record: the program is not being run.")); + if (non_stop) + error (_("Process record target can't debug inferior in non-stop mode " + "(non-stop).")); + if (target_async_permitted) + error (_("Process record target can't debug inferior in asynchronous " + "mode (target-async).")); + + if (!gdbarch_process_record_p (current_gdbarch)) + error (_("Process record: the current architecture doesn't support " + "record function.")); + + /* Check if record target is already running. */ + if (TARGET_IS_PROCESS_RECORD) + { + if (!nquery + (_("Process record target already running, do you want to delete " + "the old record log?"))) + return; + } + + push_target (&record_ops); + + /* Reset */ + record_insn_num = 0; + record_list = &record_first; + record_list->next = NULL; +} + +static void +record_close (int quitting) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); + + record_list_release (record_list); +} + +static void +record_resume (ptid_t ptid, int step, enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; + + if (!RECORD_IS_REPLAY) + { + record_message (current_gdbarch); + record_beneath_to_resume (ptid, 1, siggnal); + } +} + +static void +record_sig_handler (int signo) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); + + record_resume_step = 1; + record_get_sig = 1; +} + +static void +record_wait_cleanups (void *ignore) +{ + if (execution_direction == EXEC_REVERSE) + { + if (record_list->next) + record_list = record_list->next; + } + else + record_list = record_list->prev; + + set_executing (inferior_ptid, 0); + record_normal_stop (); +} + +/* record_wait + In replay mode, this function examines the recorded log and + determines where to stop. */ + +static ptid_t +record_wait (ptid_t ptid, struct target_waitstatus *status) +{ + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_wait " + "record_resume_step = %d\n", + record_resume_step); + + if (!RECORD_IS_REPLAY) + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (ptid, status); + } + else + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (ptid, status); + } + else + { + /* This is not a single step. */ + ptid_t ret; + int is_breakpoint = 1; + CORE_ADDR pc = 0; + int pc_is_read = 0; + struct bp_location *bl; + struct breakpoint *b; + + do + { + ret = record_beneath_to_wait (ptid, status); + + if (status->kind == TARGET_WAITKIND_STOPPED + && status->value.sig == TARGET_SIGNAL_TRAP) + { + /* Check if there is a breakpoint. */ + pc_is_read = 0; + registers_changed (); + for (bl = bp_location_chain; bl; bl = bl->global_next) + { + b = bl->owner; + gdb_assert (b); + if (b->enable_state != bp_enabled + && b->enable_state != bp_permanent) + continue; + if (!pc_is_read) + { + pc = + regcache_read_pc (get_thread_regcache (ret)); + pc_is_read = 1; + } + switch (b->type) + { + default: + if (bl->address == pc) + goto breakpoint; + break; + + case bp_watchpoint: + /* XXX teawater: I still not very clear how to + deal with it. */ + goto breakpoint; + break; + + case bp_catchpoint: + gdb_assert (b->ops != NULL + && b->ops->breakpoint_hit != NULL); + if (b->ops->breakpoint_hit (b)) + goto breakpoint; + break; + + case bp_hardware_watchpoint: + case bp_read_watchpoint: + case bp_access_watchpoint: + if (STOPPED_BY_WATCHPOINT (0)) + goto breakpoint; + break; + } + } + + /* There is not a breakpoint. */ + record_message (current_gdbarch); + record_beneath_to_resume (ptid, 1, + record_resume_siggnal); + continue; + } + + is_breakpoint = 0; + + breakpoint: + /* Add gdbarch_decr_pc_after_break to pc because gdb will + expect the pc to be at address plus decr_pc_after_break + when the inferior stops at a breakpoint. */ + if (is_breakpoint) + { + CORE_ADDR decr_pc_after_break = + gdbarch_decr_pc_after_break (current_gdbarch); + if (decr_pc_after_break) + { + if (!pc_is_read) + pc = + regcache_read_pc (get_thread_regcache (ret)); + regcache_write_pc (get_thread_regcache (ret), + pc + decr_pc_after_break); + } + } + + break; + } + while (1); + + return ret; + } + } + } + else + { + int need_dasm = 0; + struct regcache *regcache = get_current_regcache (); + int continue_flag = 1; + int first_record_end = 1; + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); + CORE_ADDR tmp_pc; + + status->kind = TARGET_WAITKIND_STOPPED; + + /* Check breakpoint when forward execute. */ + if (execution_direction == EXEC_FORWARD) + { + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + goto replay_out; + } + } + + record_get_sig = 0; + signal (SIGINT, record_sig_handler); + /* If GDB is in terminal_inferior mode, it will not get the signal. + And in GDB replay mode, GDB doesn't need to be in terminal_inferior + mode, because inferior will not executed. + Then set it to terminal_ours to make GDB get the signal. */ + target_terminal_ours (); + + /* In EXEC_FORWARD mode, record_list points to the tail of prev + instruction. */ + if (execution_direction == EXEC_FORWARD && record_list->next) + record_list = record_list->next; + + /* Loop over the record_list, looking for the next place to + stop. */ + do + { + /* Check for beginning and end of log. */ + if (execution_direction == EXEC_REVERSE + && record_list == &record_first) + { + /* Hit beginning of record log in reverse. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + if (execution_direction != EXEC_REVERSE && !record_list->next) + { + /* Hit end of record log going forward. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + + /* Set ptid, register and memory according to record_list. */ + if (record_list->type == record_reg) + { + /* reg */ + gdb_byte reg[MAX_REGISTER_SIZE]; + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (record_list), + record_list->u.reg.num); + regcache_cooked_read (regcache, record_list->u.reg.num, reg); + regcache_cooked_write (regcache, record_list->u.reg.num, + record_list->u.reg.val); + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); + } + else if (record_list->type == record_mem) + { + /* mem */ + gdb_byte *mem = alloca (record_list->u.mem.len); + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = 0x%s len = %d.\n", + host_address_to_string (record_list), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory + (record_list->u.mem.addr, mem, record_list->u.mem.len)) + error (_("Process record: error reading memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_write_memory + (record_list->u.mem.addr, record_list->u.mem.val, + record_list->u.mem.len)) + error (_ + ("Process record: error writing memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); + } + else + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_end %s to " + "inferior need_dasm = %d.\n", + host_address_to_string (record_list), + record_list->u.need_dasm); + + if (execution_direction == EXEC_FORWARD) + need_dasm = record_list->u.need_dasm; + if (need_dasm) + gdbarch_process_record_dasm (current_gdbarch); + + if (first_record_end && execution_direction == EXEC_REVERSE) + { + /* When reverse excute, the first record_end is the part of + current instruction. */ + first_record_end = 0; + } + else + { + /* In EXEC_REVERSE mode, this is the record_end of prev + instruction. + In EXEC_FORWARD mode, this is the record_end of current + instruction. */ + /* step */ + if (record_resume_step) + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: step.\n"); + continue_flag = 0; + } + + /* check breakpoint */ + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break " + "at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && execution_direction == EXEC_FORWARD + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + continue_flag = 0; + } + } + if (execution_direction == EXEC_REVERSE) + need_dasm = record_list->u.need_dasm; + } + +next: + if (continue_flag) + { + if (execution_direction == EXEC_REVERSE) + { + if (record_list->prev) + record_list = record_list->prev; + } + else + { + if (record_list->next) + record_list = record_list->next; + } + } + } + while (continue_flag); + + signal (SIGINT, handle_sigint); + +replay_out: + if (record_get_sig) + status->value.sig = TARGET_SIGNAL_INT; + else + status->value.sig = TARGET_SIGNAL_TRAP; + + discard_cleanups (old_cleanups); + } + + do_cleanups (set_cleanups); + return inferior_ptid; +} + +static void +record_disconnect (struct target_ops *target, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); + + unpush_target (&record_ops); + target_disconnect (args, from_tty); +} + +static void +record_detach (struct target_ops *ops, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); + + unpush_target (&record_ops); + target_detach (args, from_tty); +} + +static void +record_mourn_inferior (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: " + "record_mourn_inferior\n"); + + unpush_target (&record_ops); + target_mourn_inferior (); +} + +/* Close process record target before killing the inferior process. */ +static void +record_kill (void) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); + + unpush_target (&record_ops); + target_kill (); +} + +/* Record registers change (by user or by GDB) to list as an instruction. */ +static void +record_registers_change (struct regcache *regcache, int regnum) +{ + /* Check record_insn_num. */ + record_check_insn_num (0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + record_regcache = get_current_regcache (); + + if (regnum < 0) + { + int i; + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) + { + if (record_arch_list_add_reg (i)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + } + else + { + if (record_arch_list_add_reg (regnum)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +static void +record_store_registers (struct regcache *regcache, int regno) +{ + if (!record_gdb_operation_disable) + { + if (RECORD_IS_REPLAY) + { + int n; + struct cleanup *old_cleanups; + + /* Let user choose if he wants to write register or not. */ + if (regno < 0) + n = + nquery (_("Because GDB is in replay mode, changing the " + "value of a register will make the execution " + "log unusable from this point onward. " + "Change all registers?")); + else + n = + nquery (_("Because GDB is in replay mode, changing the value " + "of a register will make the execution log unusable " + "from this point onward. Change register %s?"), + gdbarch_register_name (get_regcache_arch (regcache), + regno)); + + if (!n) + { + /* Invalidate the value of regcache that was set in function + "regcache_raw_write". */ + if (regno < 0) + { + int i; + for (i = 0; + i < gdbarch_num_regs (get_regcache_arch (regcache)); + i++) + regcache_invalidate (regcache, i); + } + else + regcache_invalidate (regcache, regno); + + error (_("Process record canceled the operation.")); + } + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + record_registers_change (regcache, regno); + } + record_beneath_to_store_registers (regcache, regno); +} + +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. + In replay mode, we cannot write memory unles we are willing to + invalidate the record/replay log from this point forward. */ + +static LONGEST +record_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte * readbuf, + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) +{ + if (!record_gdb_operation_disable + && (object == TARGET_OBJECT_MEMORY + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + { + if (RECORD_IS_REPLAY) + { + /* Let user choose if he wants to write memory or not. */ + if (!nquery (_("Because GDB is in replay mode, writing to memory " + "will make the execution log unusable from this " + "point onward. Write memory at address 0x%s?"), + paddr_nz (offset))) + return -1; + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + /* Check record_insn_num */ + record_check_insn_num (0); + + /* Record registers change to list as an instruction. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + if (record_arch_list_add_mem (offset, len)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; + } + + return record_beneath_to_xfer_partial (ops, object, annex, readbuf, + writebuf, offset, len); +} + +/* record_insert_breakpoint + record_remove_breakpoint + Behavior is conditional on RECORD_IS_REPLAY. + We will not actually insert or remove breakpoints when replaying, + nor when recording. */ + +static int +record_insert_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_insert_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_remove_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_remove_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_can_execute_reverse (void) +{ + return 1; +} + +static void +init_record_ops (void) +{ + record_ops.to_shortname = "record"; + record_ops.to_longname = "Process record and replay target"; + record_ops.to_doc = + "Log program while executing and replay execution from log."; + record_ops.to_open = record_open; + record_ops.to_close = record_close; + record_ops.to_resume = record_resume; + record_ops.to_wait = record_wait; + record_ops.to_disconnect = record_disconnect; + record_ops.to_detach = record_detach; + record_ops.to_mourn_inferior = record_mourn_inferior; + record_ops.to_kill = record_kill; + record_ops.to_create_inferior = find_default_create_inferior; + record_ops.to_store_registers = record_store_registers; + record_ops.to_xfer_partial = record_xfer_partial; + record_ops.to_insert_breakpoint = record_insert_breakpoint; + record_ops.to_remove_breakpoint = record_remove_breakpoint; + record_ops.to_can_execute_reverse = record_can_execute_reverse; + record_ops.to_stratum = record_stratum; + record_ops.to_magic = OPS_MAGIC; +} + +static void +show_record_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), + value); +} + +/* cmd_record_start -- alias for "target record". */ + +static void +cmd_record_start (char *args, int from_tty) +{ + execute_command ("target record", from_tty); +} + +/* cmd_record_delete -- truncate the record log from the present point + of replay until the end. */ + +static void +cmd_record_delete (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (RECORD_IS_REPLAY) + { + if (!from_tty || query (_("Delete the log from this point forward " + "and begin to record the running message " + "at current PC?"))) + record_list_release_next (); + } + else + printf_unfiltered (_("Already at end of record list.\n")); + + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* cmd_record_stop -- implement the "stoprecord" command. */ + +static void +cmd_record_stop (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (!record_list || !from_tty || query (_("Delete recorded log and " + "stop recording?"))) + unpush_target (&record_ops); + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* set_record_insn_max_num -- set upper limit of record log size. */ + +static void +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) +{ + if (record_insn_num > record_insn_max_num && record_insn_max_num) + { + printf_unfiltered (_("Record instructions number is bigger than " + "record instructions max number. Auto delete " + "the first ones?\n")); + + while (record_insn_num > record_insn_max_num) + record_list_release_first (); + } +} + +/* show_record_insn_number -- print the current index + into the record log (number of insns recorded so far). */ + +static void +show_record_insn_number (char *ignore, int from_tty) +{ + printf_unfiltered (_("Record instruction number is %d.\n"), + record_insn_num); +} + +void +_initialize_record (void) +{ + /* Init record_maskall. */ + if (sigfillset (&record_maskall) == -1) + perror_with_name (_("Process record: sigfillset failed")); + + /* Init record_first. */ + record_first.prev = NULL; + record_first.next = NULL; + record_first.type = record_end; + record_first.u.need_dasm = 0; + + init_record_ops (); + add_target (&record_ops); + + add_setshow_zinteger_cmd ("record", no_class, &record_debug, + _("Set debugging of record/replay feature."), + _("Show debugging of record/replay feature."), + _("When enabled, debugging output for " + "record/replay feature is displayed."), + NULL, show_record_debug, &setdebuglist, + &showdebuglist); + + add_com ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command.")); + + add_com_alias ("rec", "record", class_obscure, 1); + + /* XXX: I try to use some simple commands such as "disconnect" and + "detach" to support this functions. But these commands all have + other affect to GDB such as call function "no_shared_libraries". + So I add special commands to GDB. */ + add_com ("delrecord", class_obscure, cmd_record_delete, + _("Delete the rest of execution log and start recording it anew.")); + add_com_alias ("dr", "delrecord", class_obscure, 1); + add_com ("stoprecord", class_obscure, cmd_record_stop, + _("Stop the record/replay target.")); + add_com_alias ("sr", "stoprecord", class_obscure, 1); + + /* Record instructions number limit command. */ + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, + &record_stop_at_limit, + _("Set whether record/replay stop when " + "record/replay buffer becomes full."), + _("Show whether record/replay stop when " + "record/replay buffer becomes full."), + _("Enable is default value.\n" + "When enabled, if the record/replay buffer " + "becomes full,\n" + "ask user what to do.\n" + "When disabled, if the record/replay buffer " + "becomes full,\n" + "delete it and start new recording."), + NULL, NULL, &setlist, &showlist); + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, + &record_insn_max_num, + _("Set record/replay buffer limit."), + _("Show record/replay buffer limit."), + _("Set the maximum number of instructions to be " + "stored in the\n" + "record/replay buffer. " + "Zero means unlimited (default 200000)."), + set_record_insn_max_num, + NULL, &setlist, &showlist); + add_info ("record-insn-number", show_record_insn_number, + _("Show the current number of instructions in the " + "record/replay buffer.")); +} --- /dev/null +++ b/record.h @@ -0,0 +1,101 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 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/>. */ + +#ifndef _RECORD_H_ +#define _RECORD_H_ + +#define TARGET_IS_PROCESS_RECORD \ + (current_target.beneath == &record_ops) +#define RECORD_IS_REPLAY \ + (record_list->next || execution_direction == EXEC_REVERSE) + +typedef struct record_reg_s +{ + int num; + gdb_byte *val; +} record_reg_t; + +typedef struct record_mem_s +{ + CORE_ADDR addr; + int len; + gdb_byte *val; +} record_mem_t; + +enum record_type +{ + record_end = 0, + record_reg, + record_mem +}; + +/* This is the core struct of record function. + + An entity of record_t is a record of the value change of a register + ("record_reg") or a part of memory ("record_mem"). And each + instruction must has a record_t ("record_end") that points out this + is the last record_t of this instruction. + + Each record_t is linked to "record_list" by "prev" and "next". + */ +typedef struct record_s +{ + struct record_s *prev; + struct record_s *next; + enum record_type type; + union + { + /* reg */ + record_reg_t reg; + /* mem */ + record_mem_t mem; + /* end */ + int need_dasm; + } u; +} record_t; + +extern int record_debug; +extern record_t *record_list; +extern record_t *record_arch_list_head; +extern record_t *record_arch_list_tail; +extern struct regcache *record_regcache; + +extern struct target_ops record_ops; + +extern int record_arch_list_add_reg (int num); +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); +extern int record_arch_list_add_end (int need_dasm); +extern void record_message (struct gdbarch *gdbarch); +extern struct cleanup * record_gdb_operation_disable_set (void); + +extern void (*record_beneath_to_resume) (ptid_t, int, enum target_signal); +extern ptid_t (*record_beneath_to_wait) (ptid_t, struct target_waitstatus *); +extern void (*record_beneath_to_store_registers) (struct regcache *, + int regno); +extern LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, + LONGEST len); +extern int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); +extern int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); + +#endif /* _RECORD_H_ */ ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-02-02 9:05 ` teawater @ 2009-02-08 13:03 ` teawater 2009-02-17 7:12 ` teawater 0 siblings, 1 reply; 38+ messages in thread From: teawater @ 2009-02-08 13:03 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 179 bytes --] Hi Pedro, According to your mail "Get rid of linux-thread-db.c:target_beneath". I get ride of this global beneath function pointers. Do you think this patch is OK? Thanks, Hui [-- Attachment #2: 3-record_target.txt --] [-- Type: text/plain, Size: 39009 bytes --] --- Makefile.in | 4 record.c | 1259 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ record.h | 87 ++++ 3 files changed, 1348 insertions(+), 2 deletions(-) --- a/Makefile.in +++ b/Makefile.in @@ -661,7 +661,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr valarith.c valops.c valprint.c value.c varobj.c vec.c \ wrapper.c \ xml-tdesc.c xml-support.c \ - inferior.c + inferior.c record.c LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c @@ -812,7 +812,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ solib.o solib-null.o \ prologue-value.o memory-map.o xml-support.o \ target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ - inferior.o osdata.o + inferior.o osdata.o record.o TSOBS = inflow.o --- /dev/null +++ b/record.c @@ -0,0 +1,1259 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "target.h" +#include "gdbcmd.h" +#include "regcache.h" +#include "inferior.h" +#include "gdbthread.h" +#include "event-top.h" +#include "annotate.h" +#include "observer.h" +#include "record.h" + +#include <signal.h> + +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 + +int record_debug = 0; + +record_t record_first; +record_t *record_list = &record_first; +record_t *record_arch_list_head = NULL; +record_t *record_arch_list_tail = NULL; +struct regcache *record_regcache = NULL; + +/* 1 ask user. 0 auto delete the last record_t. */ +static int record_stop_at_limit = 1; +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; +static int record_insn_num = 0; + +struct target_ops record_ops; +static int record_resume_step = 0; +static enum target_signal record_resume_siggnal; +static int record_get_sig = 0; +static sigset_t record_maskall; +static int record_gdb_operation_disable = 0; +int record_will_store_registers = 0; + +extern struct bp_location *bp_location_chain; + +/* The beneath function pointers. */ +static void (*record_beneath_to_resume) (ptid_t, int, enum target_signal) = + NULL; +static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *) = NULL; +static void (*record_beneath_to_store_registers) (struct regcache *, + int regno) = NULL; +static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, + LONGEST len) = NULL; +static int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *) = + NULL; +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *) = + NULL; + +static void +record_list_release (record_t * rec) +{ + record_t *tmp; + + if (!rec) + return; + + while (rec->next) + { + rec = rec->next; + } + + while (rec->prev) + { + tmp = rec; + rec = rec->prev; + if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + } + + if (rec != &record_first) + xfree (rec); +} + +static void +record_list_release_next (void) +{ + record_t *rec = record_list; + record_t *tmp = rec->next; + rec->next = NULL; + while (tmp) + { + rec = tmp->next; + if (tmp->type == record_reg) + record_insn_num--; + else if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + tmp = rec; + } +} + +static void +record_list_release_first (void) +{ + record_t *tmp = NULL; + enum record_type type; + + if (!record_first.next) + return; + + while (1) + { + type = record_first.next->type; + + if (type == record_reg) + xfree (record_first.next->u.reg.val); + else if (type == record_mem) + xfree (record_first.next->u.mem.val); + tmp = record_first.next; + record_first.next = tmp->next; + xfree (tmp); + + if (!record_first.next) + { + gdb_assert (record_insn_num == 1); + break; + } + + record_first.next->prev = &record_first; + + if (type == record_end) + break; + } + + record_insn_num--; +} + +/* Add a record_t to record_arch_list. */ +static void +record_arch_list_add (record_t * rec) +{ + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_arch_list_add %s.\n", + host_address_to_string (rec)); + + if (record_arch_list_tail) + { + record_arch_list_tail->next = rec; + rec->prev = record_arch_list_tail; + record_arch_list_tail = rec; + } + else + { + record_arch_list_head = rec; + record_arch_list_tail = rec; + } +} + +/* Record the value of a register ("num") to record_arch_list. */ +int +record_arch_list_add_reg (int num) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add register num = %d to " + "record list.\n", + num); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + rec->u.reg.num = num; + + regcache_raw_read (record_regcache, num, rec->u.reg.val); + + record_arch_list_add (rec); + + return 0; +} + +/* Record the value of a region of memory whose address is "addr" and + length is "len" to record_arch_list. */ + +int +record_arch_list_add_mem (CORE_ADDR addr, int len) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add mem addr = 0x%s len = %d to " + "record list.\n", + paddr_nz (addr), len); + + if (!addr) + return 0; + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.mem.val = (gdb_byte *) xmalloc (len); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + rec->u.mem.addr = addr; + rec->u.mem.len = len; + + if (target_read_memory (addr, rec->u.mem.val, len)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: error reading memory at " + "addr = 0x%s len = %d.\n", + paddr_nz (addr), len); + xfree (rec->u.mem.val); + xfree (rec); + return -1; + } + + record_arch_list_add (rec); + + return 0; +} + +/* Add a record_end type record_t to record_arch_list. */ +int +record_arch_list_add_end (int need_dasm) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add end need_dasm = %d to " + "arch list.\n", + need_dasm); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + + rec->u.need_dasm = need_dasm; + + record_arch_list_add (rec); + + return 0; +} + +static void +record_check_insn_num (int set_terminal) +{ + if (record_insn_max_num) + { + gdb_assert (record_insn_num <= record_insn_max_num); + if (record_insn_num == record_insn_max_num) + { + /* Ask user what to do. */ + if (record_stop_at_limit) + { + int q; + if (set_terminal) + target_terminal_ours (); + q = yquery (_("Do you want to auto delete previous execution " + "log entries when record/replay buffer becomes " + "full (record-stop-at-limit)?")); + if (set_terminal) + target_terminal_inferior (); + if (q) + record_stop_at_limit = 0; + else + error (_("Process record: inferior program stopped.")); + } + } + } +} + +static void +record_normal_stop (void) +{ + finish_thread_state (minus_one_ptid); + + if (!breakpoints_always_inserted_mode () && target_has_execution) + remove_breakpoints (); + + target_terminal_ours (); + + if (target_has_stack && !stop_stack_dummy) + set_current_sal_from_frame (get_current_frame (), 1); + + select_frame (get_current_frame ()); + + annotate_stopped (); + if (!suppress_stop_observer) + { + if (!ptid_equal (inferior_ptid, null_ptid)) + observer_notify_normal_stop (inferior_thread ()->stop_bpstat); + else + observer_notify_normal_stop (NULL); + } +} + +/* 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_list_release (record_arch_list_tail); + set_executing (inferior_ptid, 0); + record_normal_stop (); +} + +void +record_message (struct gdbarch *gdbarch) +{ + int ret; + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + /* Check record_insn_num. */ + record_check_insn_num (1); + + record_regcache = get_current_regcache (); + + ret = gdbarch_process_record (gdbarch, + regcache_read_pc (record_regcache)); + if (ret > 0) + error (_("Process record: inferior program stopped.")); + if (ret < 0) + error (_("Process record: failed to record execution log.")); + + discard_cleanups (old_cleanups); + + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +/* Things to clean up if we error or QUIT out of function that set + record_gdb_operation_disable (ie. command that caused target_wait to be called). */ +static void +record_gdb_operation_disable_cleanups (void *ignore) +{ + record_gdb_operation_disable = 0; +} + +struct cleanup * +record_gdb_operation_disable_set (void) +{ + struct cleanup *old_cleanups = make_cleanup (record_gdb_operation_disable_cleanups, 0); + record_gdb_operation_disable = 1; + + return old_cleanups; +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + + /* check exec */ + if (!target_has_execution) + error (_("Process record: the program is not being run.")); + if (non_stop) + error (_("Process record target can't debug inferior in non-stop mode " + "(non-stop).")); + if (target_async_permitted) + error (_("Process record target can't debug inferior in asynchronous " + "mode (target-async).")); + + if (!gdbarch_process_record_p (current_gdbarch)) + error (_("Process record: the current architecture doesn't support " + "record function.")); + + /* Check if record target is already running. */ + if (TARGET_IS_PROCESS_RECORD) + { + if (!nquery + (_("Process record target already running, do you want to delete " + "the old record log?"))) + return; + } + + /* Set the beneath function pointers. */ + for (t = current_target.beneath; t != NULL; t = t->beneath) + { + if (!record_beneath_to_resume) + record_beneath_to_resume = t->to_resume; + if (!record_beneath_to_wait) + record_beneath_to_wait = t->to_wait; + if (!record_beneath_to_store_registers) + record_beneath_to_store_registers = t->to_store_registers; + if (!record_beneath_to_xfer_partial) + record_beneath_to_xfer_partial = t->to_xfer_partial; + if (!record_beneath_to_insert_breakpoint) + record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; + if (!record_beneath_to_remove_breakpoint) + record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; + } + if (!record_beneath_to_resume) + error (_("Process record can't get to_resume.")); + if (!record_beneath_to_wait) + error (_("Process record can't get to_wait.")); + if (!record_beneath_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!record_beneath_to_xfer_partial) + error (_("Process record can't get to_xfer_partial.")); + if (!record_beneath_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!record_beneath_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); + + /* Reset */ + record_insn_num = 0; + record_list = &record_first; + record_list->next = NULL; +} + +static void +record_close (int quitting) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); + + record_list_release (record_list); +} + +static void +record_resume (ptid_t ptid, int step, enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; + + if (!RECORD_IS_REPLAY) + { + record_message (current_gdbarch); + record_beneath_to_resume (ptid, 1, siggnal); + } +} + +static void +record_sig_handler (int signo) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); + + record_resume_step = 1; + record_get_sig = 1; +} + +static void +record_wait_cleanups (void *ignore) +{ + if (execution_direction == EXEC_REVERSE) + { + if (record_list->next) + record_list = record_list->next; + } + else + record_list = record_list->prev; + + set_executing (inferior_ptid, 0); + record_normal_stop (); +} + +/* record_wait + In replay mode, this function examines the recorded log and + determines where to stop. */ + +static ptid_t +record_wait (struct target_ops *ops, + ptid_t ptid, struct target_waitstatus *status) +{ + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_wait " + "record_resume_step = %d\n", + record_resume_step); + + if (!RECORD_IS_REPLAY) + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (find_target_beneath (ops), + ptid, status); + } + else + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (find_target_beneath (ops), + ptid, status); + } + else + { + /* This is not a single step. */ + ptid_t ret; + int is_breakpoint = 1; + CORE_ADDR pc = 0; + int pc_is_read = 0; + struct bp_location *bl; + struct breakpoint *b; + + do + { + ret = record_beneath_to_wait (find_target_beneath (ops), + ptid, status); + + if (status->kind == TARGET_WAITKIND_STOPPED + && status->value.sig == TARGET_SIGNAL_TRAP) + { + /* Check if there is a breakpoint. */ + pc_is_read = 0; + registers_changed (); + for (bl = bp_location_chain; bl; bl = bl->global_next) + { + b = bl->owner; + gdb_assert (b); + if (b->enable_state != bp_enabled + && b->enable_state != bp_permanent) + continue; + if (!pc_is_read) + { + pc = + regcache_read_pc (get_thread_regcache (ret)); + pc_is_read = 1; + } + switch (b->type) + { + default: + if (bl->address == pc) + goto breakpoint; + break; + + case bp_watchpoint: + /* XXX teawater: I still not very clear how to + deal with it. */ + goto breakpoint; + break; + + case bp_catchpoint: + gdb_assert (b->ops != NULL + && b->ops->breakpoint_hit != NULL); + if (b->ops->breakpoint_hit (b)) + goto breakpoint; + break; + + case bp_hardware_watchpoint: + case bp_read_watchpoint: + case bp_access_watchpoint: + if (STOPPED_BY_WATCHPOINT (0)) + goto breakpoint; + break; + } + } + + /* There is not a breakpoint. */ + record_message (current_gdbarch); + record_beneath_to_resume (ptid, 1, + record_resume_siggnal); + continue; + } + + is_breakpoint = 0; + + breakpoint: + /* Add gdbarch_decr_pc_after_break to pc because gdb will + expect the pc to be at address plus decr_pc_after_break + when the inferior stops at a breakpoint. */ + if (is_breakpoint) + { + CORE_ADDR decr_pc_after_break = + gdbarch_decr_pc_after_break (current_gdbarch); + if (decr_pc_after_break) + { + if (!pc_is_read) + pc = + regcache_read_pc (get_thread_regcache (ret)); + regcache_write_pc (get_thread_regcache (ret), + pc + decr_pc_after_break); + } + } + + break; + } + while (1); + + return ret; + } + } + } + else + { + int need_dasm = 0; + struct regcache *regcache = get_current_regcache (); + int continue_flag = 1; + int first_record_end = 1; + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); + CORE_ADDR tmp_pc; + + status->kind = TARGET_WAITKIND_STOPPED; + + /* Check breakpoint when forward execute. */ + if (execution_direction == EXEC_FORWARD) + { + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + goto replay_out; + } + } + + record_get_sig = 0; + signal (SIGINT, record_sig_handler); + /* If GDB is in terminal_inferior mode, it will not get the signal. + And in GDB replay mode, GDB doesn't need to be in terminal_inferior + mode, because inferior will not executed. + Then set it to terminal_ours to make GDB get the signal. */ + target_terminal_ours (); + + /* In EXEC_FORWARD mode, record_list points to the tail of prev + instruction. */ + if (execution_direction == EXEC_FORWARD && record_list->next) + record_list = record_list->next; + + /* Loop over the record_list, looking for the next place to + stop. */ + do + { + /* Check for beginning and end of log. */ + if (execution_direction == EXEC_REVERSE + && record_list == &record_first) + { + /* Hit beginning of record log in reverse. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + if (execution_direction != EXEC_REVERSE && !record_list->next) + { + /* Hit end of record log going forward. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + + /* Set ptid, register and memory according to record_list. */ + if (record_list->type == record_reg) + { + /* reg */ + gdb_byte reg[MAX_REGISTER_SIZE]; + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (record_list), + record_list->u.reg.num); + regcache_cooked_read (regcache, record_list->u.reg.num, reg); + regcache_cooked_write (regcache, record_list->u.reg.num, + record_list->u.reg.val); + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); + } + else if (record_list->type == record_mem) + { + /* mem */ + gdb_byte *mem = alloca (record_list->u.mem.len); + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = 0x%s len = %d.\n", + host_address_to_string (record_list), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory + (record_list->u.mem.addr, mem, record_list->u.mem.len)) + error (_("Process record: error reading memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_write_memory + (record_list->u.mem.addr, record_list->u.mem.val, + record_list->u.mem.len)) + error (_ + ("Process record: error writing memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); + } + else + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_end %s to " + "inferior need_dasm = %d.\n", + host_address_to_string (record_list), + record_list->u.need_dasm); + + if (execution_direction == EXEC_FORWARD) + need_dasm = record_list->u.need_dasm; + if (need_dasm) + gdbarch_process_record_dasm (current_gdbarch); + + if (first_record_end && execution_direction == EXEC_REVERSE) + { + /* When reverse excute, the first record_end is the part of + current instruction. */ + first_record_end = 0; + } + else + { + /* In EXEC_REVERSE mode, this is the record_end of prev + instruction. + In EXEC_FORWARD mode, this is the record_end of current + instruction. */ + /* step */ + if (record_resume_step) + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: step.\n"); + continue_flag = 0; + } + + /* check breakpoint */ + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break " + "at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && execution_direction == EXEC_FORWARD + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + continue_flag = 0; + } + } + if (execution_direction == EXEC_REVERSE) + need_dasm = record_list->u.need_dasm; + } + +next: + if (continue_flag) + { + if (execution_direction == EXEC_REVERSE) + { + if (record_list->prev) + record_list = record_list->prev; + } + else + { + if (record_list->next) + record_list = record_list->next; + } + } + } + while (continue_flag); + + signal (SIGINT, handle_sigint); + +replay_out: + if (record_get_sig) + status->value.sig = TARGET_SIGNAL_INT; + else + status->value.sig = TARGET_SIGNAL_TRAP; + + discard_cleanups (old_cleanups); + } + + do_cleanups (set_cleanups); + return inferior_ptid; +} + +static void +record_disconnect (struct target_ops *target, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); + + unpush_target (&record_ops); + target_disconnect (args, from_tty); +} + +static void +record_detach (struct target_ops *ops, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); + + unpush_target (&record_ops); + target_detach (args, from_tty); +} + +static void +record_mourn_inferior (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: " + "record_mourn_inferior\n"); + + unpush_target (&record_ops); + target_mourn_inferior (); +} + +/* Close process record target before killing the inferior process. */ +static void +record_kill (void) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); + + unpush_target (&record_ops); + target_kill (); +} + +/* Record registers change (by user or by GDB) to list as an instruction. */ +static void +record_registers_change (struct regcache *regcache, int regnum) +{ + /* Check record_insn_num. */ + record_check_insn_num (0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + record_regcache = get_current_regcache (); + + if (regnum < 0) + { + int i; + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) + { + if (record_arch_list_add_reg (i)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + } + else + { + if (record_arch_list_add_reg (regnum)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +static void +record_store_registers (struct regcache *regcache, int regno) +{ + if (!record_gdb_operation_disable) + { + if (RECORD_IS_REPLAY) + { + int n; + struct cleanup *old_cleanups; + + /* Let user choose if he wants to write register or not. */ + if (regno < 0) + n = + nquery (_("Because GDB is in replay mode, changing the " + "value of a register will make the execution " + "log unusable from this point onward. " + "Change all registers?")); + else + n = + nquery (_("Because GDB is in replay mode, changing the value " + "of a register will make the execution log unusable " + "from this point onward. Change register %s?"), + gdbarch_register_name (get_regcache_arch (regcache), + regno)); + + if (!n) + { + /* Invalidate the value of regcache that was set in function + "regcache_raw_write". */ + if (regno < 0) + { + int i; + for (i = 0; + i < gdbarch_num_regs (get_regcache_arch (regcache)); + i++) + regcache_invalidate (regcache, i); + } + else + regcache_invalidate (regcache, regno); + + error (_("Process record canceled the operation.")); + } + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + record_registers_change (regcache, regno); + } + record_beneath_to_store_registers (regcache, regno); +} + +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. + In replay mode, we cannot write memory unles we are willing to + invalidate the record/replay log from this point forward. */ + +static LONGEST +record_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte * readbuf, + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) +{ + if (!record_gdb_operation_disable + && (object == TARGET_OBJECT_MEMORY + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + { + if (RECORD_IS_REPLAY) + { + /* Let user choose if he wants to write memory or not. */ + if (!nquery (_("Because GDB is in replay mode, writing to memory " + "will make the execution log unusable from this " + "point onward. Write memory at address 0x%s?"), + paddr_nz (offset))) + return -1; + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + /* Check record_insn_num */ + record_check_insn_num (0); + + /* Record registers change to list as an instruction. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + if (record_arch_list_add_mem (offset, len)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; + } + + return record_beneath_to_xfer_partial (find_target_beneath (ops), object, + annex, readbuf, writebuf, offset, + len); +} + +/* record_insert_breakpoint + record_remove_breakpoint + Behavior is conditional on RECORD_IS_REPLAY. + We will not actually insert or remove breakpoints when replaying, + nor when recording. */ + +static int +record_insert_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_insert_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_remove_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_remove_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_can_execute_reverse (void) +{ + return 1; +} + +static void +init_record_ops (void) +{ + record_ops.to_shortname = "record"; + record_ops.to_longname = "Process record and replay target"; + record_ops.to_doc = + "Log program while executing and replay execution from log."; + record_ops.to_open = record_open; + record_ops.to_close = record_close; + record_ops.to_resume = record_resume; + record_ops.to_wait = record_wait; + record_ops.to_disconnect = record_disconnect; + record_ops.to_detach = record_detach; + record_ops.to_mourn_inferior = record_mourn_inferior; + record_ops.to_kill = record_kill; + record_ops.to_create_inferior = find_default_create_inferior; + record_ops.to_store_registers = record_store_registers; + record_ops.to_xfer_partial = record_xfer_partial; + record_ops.to_insert_breakpoint = record_insert_breakpoint; + record_ops.to_remove_breakpoint = record_remove_breakpoint; + record_ops.to_can_execute_reverse = record_can_execute_reverse; + record_ops.to_stratum = record_stratum; + record_ops.to_magic = OPS_MAGIC; +} + +static void +show_record_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), + value); +} + +/* cmd_record_start -- alias for "target record". */ + +static void +cmd_record_start (char *args, int from_tty) +{ + execute_command ("target record", from_tty); +} + +/* cmd_record_delete -- truncate the record log from the present point + of replay until the end. */ + +static void +cmd_record_delete (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (RECORD_IS_REPLAY) + { + if (!from_tty || query (_("Delete the log from this point forward " + "and begin to record the running message " + "at current PC?"))) + record_list_release_next (); + } + else + printf_unfiltered (_("Already at end of record list.\n")); + + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* cmd_record_stop -- implement the "stoprecord" command. */ + +static void +cmd_record_stop (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (!record_list || !from_tty || query (_("Delete recorded log and " + "stop recording?"))) + unpush_target (&record_ops); + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* set_record_insn_max_num -- set upper limit of record log size. */ + +static void +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) +{ + if (record_insn_num > record_insn_max_num && record_insn_max_num) + { + printf_unfiltered (_("Record instructions number is bigger than " + "record instructions max number. Auto delete " + "the first ones?\n")); + + while (record_insn_num > record_insn_max_num) + record_list_release_first (); + } +} + +/* show_record_insn_number -- print the current index + into the record log (number of insns recorded so far). */ + +static void +show_record_insn_number (char *ignore, int from_tty) +{ + printf_unfiltered (_("Record instruction number is %d.\n"), + record_insn_num); +} + +void +_initialize_record (void) +{ + /* Init record_maskall. */ + if (sigfillset (&record_maskall) == -1) + perror_with_name (_("Process record: sigfillset failed")); + + /* Init record_first. */ + record_first.prev = NULL; + record_first.next = NULL; + record_first.type = record_end; + record_first.u.need_dasm = 0; + + init_record_ops (); + add_target (&record_ops); + + add_setshow_zinteger_cmd ("record", no_class, &record_debug, + _("Set debugging of record/replay feature."), + _("Show debugging of record/replay feature."), + _("When enabled, debugging output for " + "record/replay feature is displayed."), + NULL, show_record_debug, &setdebuglist, + &showdebuglist); + + add_com ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command.")); + + add_com_alias ("rec", "record", class_obscure, 1); + + /* XXX: I try to use some simple commands such as "disconnect" and + "detach" to support this functions. But these commands all have + other affect to GDB such as call function "no_shared_libraries". + So I add special commands to GDB. */ + add_com ("delrecord", class_obscure, cmd_record_delete, + _("Delete the rest of execution log and start recording it anew.")); + add_com_alias ("dr", "delrecord", class_obscure, 1); + add_com ("stoprecord", class_obscure, cmd_record_stop, + _("Stop the record/replay target.")); + add_com_alias ("sr", "stoprecord", class_obscure, 1); + + /* Record instructions number limit command. */ + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, + &record_stop_at_limit, + _("Set whether record/replay stop when " + "record/replay buffer becomes full."), + _("Show whether record/replay stop when " + "record/replay buffer becomes full."), + _("Enable is default value.\n" + "When enabled, if the record/replay buffer " + "becomes full,\n" + "ask user what to do.\n" + "When disabled, if the record/replay buffer " + "becomes full,\n" + "delete it and start new recording."), + NULL, NULL, &setlist, &showlist); + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, + &record_insn_max_num, + _("Set record/replay buffer limit."), + _("Show record/replay buffer limit."), + _("Set the maximum number of instructions to be " + "stored in the\n" + "record/replay buffer. " + "Zero means unlimited (default 200000)."), + set_record_insn_max_num, + NULL, &setlist, &showlist); + add_info ("record-insn-number", show_record_insn_number, + _("Show the current number of instructions in the " + "record/replay buffer.")); +} --- /dev/null +++ b/record.h @@ -0,0 +1,87 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 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/>. */ + +#ifndef _RECORD_H_ +#define _RECORD_H_ + +#define TARGET_IS_PROCESS_RECORD \ + (current_target.beneath == &record_ops) +#define RECORD_IS_REPLAY \ + (record_list->next || execution_direction == EXEC_REVERSE) + +typedef struct record_reg_s +{ + int num; + gdb_byte *val; +} record_reg_t; + +typedef struct record_mem_s +{ + CORE_ADDR addr; + int len; + gdb_byte *val; +} record_mem_t; + +enum record_type +{ + record_end = 0, + record_reg, + record_mem +}; + +/* This is the core struct of record function. + + An entity of record_t is a record of the value change of a register + ("record_reg") or a part of memory ("record_mem"). And each + instruction must has a record_t ("record_end") that points out this + is the last record_t of this instruction. + + Each record_t is linked to "record_list" by "prev" and "next". + */ +typedef struct record_s +{ + struct record_s *prev; + struct record_s *next; + enum record_type type; + union + { + /* reg */ + record_reg_t reg; + /* mem */ + record_mem_t mem; + /* end */ + int need_dasm; + } u; +} record_t; + +extern int record_debug; +extern record_t *record_list; +extern record_t *record_arch_list_head; +extern record_t *record_arch_list_tail; +extern struct regcache *record_regcache; + +extern struct target_ops record_ops; + +extern int record_arch_list_add_reg (int num); +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); +extern int record_arch_list_add_end (int need_dasm); +extern void record_message (struct gdbarch *gdbarch); +extern struct cleanup * record_gdb_operation_disable_set (void); + +#endif /* _RECORD_H_ */ ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-02-08 13:03 ` teawater @ 2009-02-17 7:12 ` teawater 2009-02-17 7:21 ` teawater 2009-02-23 14:08 ` teawater 0 siblings, 2 replies; 38+ messages in thread From: teawater @ 2009-02-17 7:12 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 385 bytes --] Update follow the cvs-head. It just has a very small change - add a 0 to be the argument of observer_notify_normal_stop. On Sun, Feb 8, 2009 at 21:03, teawater <teawater@gmail.com> wrote: > Hi Pedro, > > According to your mail "Get rid of linux-thread-db.c:target_beneath". > I get ride of this global beneath function pointers. > > Do you think this patch is OK? > > Thanks, > Hui > [-- Attachment #2: 3-record_target.txt --] [-- Type: text/plain, Size: 39015 bytes --] --- Makefile.in | 4 record.c | 1259 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ record.h | 87 ++++ 3 files changed, 1348 insertions(+), 2 deletions(-) --- a/Makefile.in +++ b/Makefile.in @@ -661,7 +661,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr valarith.c valops.c valprint.c value.c varobj.c vec.c \ wrapper.c \ xml-tdesc.c xml-support.c \ - inferior.c + inferior.c record.c LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c @@ -812,7 +812,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ solib.o solib-null.o \ prologue-value.o memory-map.o xml-support.o \ target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ - inferior.o osdata.o + inferior.o osdata.o record.o TSOBS = inflow.o --- /dev/null +++ b/record.c @@ -0,0 +1,1259 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "target.h" +#include "gdbcmd.h" +#include "regcache.h" +#include "inferior.h" +#include "gdbthread.h" +#include "event-top.h" +#include "annotate.h" +#include "observer.h" +#include "record.h" + +#include <signal.h> + +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 + +int record_debug = 0; + +record_t record_first; +record_t *record_list = &record_first; +record_t *record_arch_list_head = NULL; +record_t *record_arch_list_tail = NULL; +struct regcache *record_regcache = NULL; + +/* 1 ask user. 0 auto delete the last record_t. */ +static int record_stop_at_limit = 1; +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; +static int record_insn_num = 0; + +struct target_ops record_ops; +static int record_resume_step = 0; +static enum target_signal record_resume_siggnal; +static int record_get_sig = 0; +static sigset_t record_maskall; +static int record_gdb_operation_disable = 0; +int record_will_store_registers = 0; + +extern struct bp_location *bp_location_chain; + +/* The beneath function pointers. */ +static void (*record_beneath_to_resume) (ptid_t, int, enum target_signal) = + NULL; +static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *) = NULL; +static void (*record_beneath_to_store_registers) (struct regcache *, + int regno) = NULL; +static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, + LONGEST len) = NULL; +static int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *) = + NULL; +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *) = + NULL; + +static void +record_list_release (record_t * rec) +{ + record_t *tmp; + + if (!rec) + return; + + while (rec->next) + { + rec = rec->next; + } + + while (rec->prev) + { + tmp = rec; + rec = rec->prev; + if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + } + + if (rec != &record_first) + xfree (rec); +} + +static void +record_list_release_next (void) +{ + record_t *rec = record_list; + record_t *tmp = rec->next; + rec->next = NULL; + while (tmp) + { + rec = tmp->next; + if (tmp->type == record_reg) + record_insn_num--; + else if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + tmp = rec; + } +} + +static void +record_list_release_first (void) +{ + record_t *tmp = NULL; + enum record_type type; + + if (!record_first.next) + return; + + while (1) + { + type = record_first.next->type; + + if (type == record_reg) + xfree (record_first.next->u.reg.val); + else if (type == record_mem) + xfree (record_first.next->u.mem.val); + tmp = record_first.next; + record_first.next = tmp->next; + xfree (tmp); + + if (!record_first.next) + { + gdb_assert (record_insn_num == 1); + break; + } + + record_first.next->prev = &record_first; + + if (type == record_end) + break; + } + + record_insn_num--; +} + +/* Add a record_t to record_arch_list. */ +static void +record_arch_list_add (record_t * rec) +{ + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_arch_list_add %s.\n", + host_address_to_string (rec)); + + if (record_arch_list_tail) + { + record_arch_list_tail->next = rec; + rec->prev = record_arch_list_tail; + record_arch_list_tail = rec; + } + else + { + record_arch_list_head = rec; + record_arch_list_tail = rec; + } +} + +/* Record the value of a register ("num") to record_arch_list. */ +int +record_arch_list_add_reg (int num) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add register num = %d to " + "record list.\n", + num); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + rec->u.reg.num = num; + + regcache_raw_read (record_regcache, num, rec->u.reg.val); + + record_arch_list_add (rec); + + return 0; +} + +/* Record the value of a region of memory whose address is "addr" and + length is "len" to record_arch_list. */ + +int +record_arch_list_add_mem (CORE_ADDR addr, int len) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add mem addr = 0x%s len = %d to " + "record list.\n", + paddr_nz (addr), len); + + if (!addr) + return 0; + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.mem.val = (gdb_byte *) xmalloc (len); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + rec->u.mem.addr = addr; + rec->u.mem.len = len; + + if (target_read_memory (addr, rec->u.mem.val, len)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: error reading memory at " + "addr = 0x%s len = %d.\n", + paddr_nz (addr), len); + xfree (rec->u.mem.val); + xfree (rec); + return -1; + } + + record_arch_list_add (rec); + + return 0; +} + +/* Add a record_end type record_t to record_arch_list. */ +int +record_arch_list_add_end (int need_dasm) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add end need_dasm = %d to " + "arch list.\n", + need_dasm); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + + rec->u.need_dasm = need_dasm; + + record_arch_list_add (rec); + + return 0; +} + +static void +record_check_insn_num (int set_terminal) +{ + if (record_insn_max_num) + { + gdb_assert (record_insn_num <= record_insn_max_num); + if (record_insn_num == record_insn_max_num) + { + /* Ask user what to do. */ + if (record_stop_at_limit) + { + int q; + if (set_terminal) + target_terminal_ours (); + q = yquery (_("Do you want to auto delete previous execution " + "log entries when record/replay buffer becomes " + "full (record-stop-at-limit)?")); + if (set_terminal) + target_terminal_inferior (); + if (q) + record_stop_at_limit = 0; + else + error (_("Process record: inferior program stopped.")); + } + } + } +} + +static void +record_normal_stop (void) +{ + finish_thread_state (minus_one_ptid); + + if (!breakpoints_always_inserted_mode () && target_has_execution) + remove_breakpoints (); + + target_terminal_ours (); + + if (target_has_stack && !stop_stack_dummy) + set_current_sal_from_frame (get_current_frame (), 1); + + select_frame (get_current_frame ()); + + annotate_stopped (); + if (!suppress_stop_observer) + { + if (!ptid_equal (inferior_ptid, null_ptid)) + observer_notify_normal_stop (inferior_thread ()->stop_bpstat, 0); + else + observer_notify_normal_stop (NULL, 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_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_list_release (record_arch_list_tail); + set_executing (inferior_ptid, 0); + record_normal_stop (); +} + +void +record_message (struct gdbarch *gdbarch) +{ + int ret; + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + /* Check record_insn_num. */ + record_check_insn_num (1); + + record_regcache = get_current_regcache (); + + ret = gdbarch_process_record (gdbarch, + regcache_read_pc (record_regcache)); + if (ret > 0) + error (_("Process record: inferior program stopped.")); + if (ret < 0) + error (_("Process record: failed to record execution log.")); + + discard_cleanups (old_cleanups); + + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +/* Things to clean up if we error or QUIT out of function that set + record_gdb_operation_disable (ie. command that caused target_wait to be called). */ +static void +record_gdb_operation_disable_cleanups (void *ignore) +{ + record_gdb_operation_disable = 0; +} + +struct cleanup * +record_gdb_operation_disable_set (void) +{ + struct cleanup *old_cleanups = make_cleanup (record_gdb_operation_disable_cleanups, 0); + record_gdb_operation_disable = 1; + + return old_cleanups; +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + + /* check exec */ + if (!target_has_execution) + error (_("Process record: the program is not being run.")); + if (non_stop) + error (_("Process record target can't debug inferior in non-stop mode " + "(non-stop).")); + if (target_async_permitted) + error (_("Process record target can't debug inferior in asynchronous " + "mode (target-async).")); + + if (!gdbarch_process_record_p (current_gdbarch)) + error (_("Process record: the current architecture doesn't support " + "record function.")); + + /* Check if record target is already running. */ + if (TARGET_IS_PROCESS_RECORD) + { + if (!nquery + (_("Process record target already running, do you want to delete " + "the old record log?"))) + return; + } + + /* Set the beneath function pointers. */ + for (t = current_target.beneath; t != NULL; t = t->beneath) + { + if (!record_beneath_to_resume) + record_beneath_to_resume = t->to_resume; + if (!record_beneath_to_wait) + record_beneath_to_wait = t->to_wait; + if (!record_beneath_to_store_registers) + record_beneath_to_store_registers = t->to_store_registers; + if (!record_beneath_to_xfer_partial) + record_beneath_to_xfer_partial = t->to_xfer_partial; + if (!record_beneath_to_insert_breakpoint) + record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; + if (!record_beneath_to_remove_breakpoint) + record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; + } + if (!record_beneath_to_resume) + error (_("Process record can't get to_resume.")); + if (!record_beneath_to_wait) + error (_("Process record can't get to_wait.")); + if (!record_beneath_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!record_beneath_to_xfer_partial) + error (_("Process record can't get to_xfer_partial.")); + if (!record_beneath_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!record_beneath_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); + + /* Reset */ + record_insn_num = 0; + record_list = &record_first; + record_list->next = NULL; +} + +static void +record_close (int quitting) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); + + record_list_release (record_list); +} + +static void +record_resume (ptid_t ptid, int step, enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; + + if (!RECORD_IS_REPLAY) + { + record_message (current_gdbarch); + record_beneath_to_resume (ptid, 1, siggnal); + } +} + +static void +record_sig_handler (int signo) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); + + record_resume_step = 1; + record_get_sig = 1; +} + +static void +record_wait_cleanups (void *ignore) +{ + if (execution_direction == EXEC_REVERSE) + { + if (record_list->next) + record_list = record_list->next; + } + else + record_list = record_list->prev; + + set_executing (inferior_ptid, 0); + record_normal_stop (); +} + +/* record_wait + In replay mode, this function examines the recorded log and + determines where to stop. */ + +static ptid_t +record_wait (struct target_ops *ops, + ptid_t ptid, struct target_waitstatus *status) +{ + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_wait " + "record_resume_step = %d\n", + record_resume_step); + + if (!RECORD_IS_REPLAY) + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (find_target_beneath (ops), + ptid, status); + } + else + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (find_target_beneath (ops), + ptid, status); + } + else + { + /* This is not a single step. */ + ptid_t ret; + int is_breakpoint = 1; + CORE_ADDR pc = 0; + int pc_is_read = 0; + struct bp_location *bl; + struct breakpoint *b; + + do + { + ret = record_beneath_to_wait (find_target_beneath (ops), + ptid, status); + + if (status->kind == TARGET_WAITKIND_STOPPED + && status->value.sig == TARGET_SIGNAL_TRAP) + { + /* Check if there is a breakpoint. */ + pc_is_read = 0; + registers_changed (); + for (bl = bp_location_chain; bl; bl = bl->global_next) + { + b = bl->owner; + gdb_assert (b); + if (b->enable_state != bp_enabled + && b->enable_state != bp_permanent) + continue; + if (!pc_is_read) + { + pc = + regcache_read_pc (get_thread_regcache (ret)); + pc_is_read = 1; + } + switch (b->type) + { + default: + if (bl->address == pc) + goto breakpoint; + break; + + case bp_watchpoint: + /* XXX teawater: I still not very clear how to + deal with it. */ + goto breakpoint; + break; + + case bp_catchpoint: + gdb_assert (b->ops != NULL + && b->ops->breakpoint_hit != NULL); + if (b->ops->breakpoint_hit (b)) + goto breakpoint; + break; + + case bp_hardware_watchpoint: + case bp_read_watchpoint: + case bp_access_watchpoint: + if (STOPPED_BY_WATCHPOINT (0)) + goto breakpoint; + break; + } + } + + /* There is not a breakpoint. */ + record_message (current_gdbarch); + record_beneath_to_resume (ptid, 1, + record_resume_siggnal); + continue; + } + + is_breakpoint = 0; + + breakpoint: + /* Add gdbarch_decr_pc_after_break to pc because gdb will + expect the pc to be at address plus decr_pc_after_break + when the inferior stops at a breakpoint. */ + if (is_breakpoint) + { + CORE_ADDR decr_pc_after_break = + gdbarch_decr_pc_after_break (current_gdbarch); + if (decr_pc_after_break) + { + if (!pc_is_read) + pc = + regcache_read_pc (get_thread_regcache (ret)); + regcache_write_pc (get_thread_regcache (ret), + pc + decr_pc_after_break); + } + } + + break; + } + while (1); + + return ret; + } + } + } + else + { + int need_dasm = 0; + struct regcache *regcache = get_current_regcache (); + int continue_flag = 1; + int first_record_end = 1; + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); + CORE_ADDR tmp_pc; + + status->kind = TARGET_WAITKIND_STOPPED; + + /* Check breakpoint when forward execute. */ + if (execution_direction == EXEC_FORWARD) + { + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + goto replay_out; + } + } + + record_get_sig = 0; + signal (SIGINT, record_sig_handler); + /* If GDB is in terminal_inferior mode, it will not get the signal. + And in GDB replay mode, GDB doesn't need to be in terminal_inferior + mode, because inferior will not executed. + Then set it to terminal_ours to make GDB get the signal. */ + target_terminal_ours (); + + /* In EXEC_FORWARD mode, record_list points to the tail of prev + instruction. */ + if (execution_direction == EXEC_FORWARD && record_list->next) + record_list = record_list->next; + + /* Loop over the record_list, looking for the next place to + stop. */ + do + { + /* Check for beginning and end of log. */ + if (execution_direction == EXEC_REVERSE + && record_list == &record_first) + { + /* Hit beginning of record log in reverse. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + if (execution_direction != EXEC_REVERSE && !record_list->next) + { + /* Hit end of record log going forward. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + + /* Set ptid, register and memory according to record_list. */ + if (record_list->type == record_reg) + { + /* reg */ + gdb_byte reg[MAX_REGISTER_SIZE]; + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (record_list), + record_list->u.reg.num); + regcache_cooked_read (regcache, record_list->u.reg.num, reg); + regcache_cooked_write (regcache, record_list->u.reg.num, + record_list->u.reg.val); + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); + } + else if (record_list->type == record_mem) + { + /* mem */ + gdb_byte *mem = alloca (record_list->u.mem.len); + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = 0x%s len = %d.\n", + host_address_to_string (record_list), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory + (record_list->u.mem.addr, mem, record_list->u.mem.len)) + error (_("Process record: error reading memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_write_memory + (record_list->u.mem.addr, record_list->u.mem.val, + record_list->u.mem.len)) + error (_ + ("Process record: error writing memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); + } + else + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_end %s to " + "inferior need_dasm = %d.\n", + host_address_to_string (record_list), + record_list->u.need_dasm); + + if (execution_direction == EXEC_FORWARD) + need_dasm = record_list->u.need_dasm; + if (need_dasm) + gdbarch_process_record_dasm (current_gdbarch); + + if (first_record_end && execution_direction == EXEC_REVERSE) + { + /* When reverse excute, the first record_end is the part of + current instruction. */ + first_record_end = 0; + } + else + { + /* In EXEC_REVERSE mode, this is the record_end of prev + instruction. + In EXEC_FORWARD mode, this is the record_end of current + instruction. */ + /* step */ + if (record_resume_step) + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: step.\n"); + continue_flag = 0; + } + + /* check breakpoint */ + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break " + "at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && execution_direction == EXEC_FORWARD + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + continue_flag = 0; + } + } + if (execution_direction == EXEC_REVERSE) + need_dasm = record_list->u.need_dasm; + } + +next: + if (continue_flag) + { + if (execution_direction == EXEC_REVERSE) + { + if (record_list->prev) + record_list = record_list->prev; + } + else + { + if (record_list->next) + record_list = record_list->next; + } + } + } + while (continue_flag); + + signal (SIGINT, handle_sigint); + +replay_out: + if (record_get_sig) + status->value.sig = TARGET_SIGNAL_INT; + else + status->value.sig = TARGET_SIGNAL_TRAP; + + discard_cleanups (old_cleanups); + } + + do_cleanups (set_cleanups); + return inferior_ptid; +} + +static void +record_disconnect (struct target_ops *target, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); + + unpush_target (&record_ops); + target_disconnect (args, from_tty); +} + +static void +record_detach (struct target_ops *ops, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); + + unpush_target (&record_ops); + target_detach (args, from_tty); +} + +static void +record_mourn_inferior (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: " + "record_mourn_inferior\n"); + + unpush_target (&record_ops); + target_mourn_inferior (); +} + +/* Close process record target before killing the inferior process. */ +static void +record_kill (void) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); + + unpush_target (&record_ops); + target_kill (); +} + +/* Record registers change (by user or by GDB) to list as an instruction. */ +static void +record_registers_change (struct regcache *regcache, int regnum) +{ + /* Check record_insn_num. */ + record_check_insn_num (0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + record_regcache = get_current_regcache (); + + if (regnum < 0) + { + int i; + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) + { + if (record_arch_list_add_reg (i)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + } + else + { + if (record_arch_list_add_reg (regnum)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +static void +record_store_registers (struct regcache *regcache, int regno) +{ + if (!record_gdb_operation_disable) + { + if (RECORD_IS_REPLAY) + { + int n; + struct cleanup *old_cleanups; + + /* Let user choose if he wants to write register or not. */ + if (regno < 0) + n = + nquery (_("Because GDB is in replay mode, changing the " + "value of a register will make the execution " + "log unusable from this point onward. " + "Change all registers?")); + else + n = + nquery (_("Because GDB is in replay mode, changing the value " + "of a register will make the execution log unusable " + "from this point onward. Change register %s?"), + gdbarch_register_name (get_regcache_arch (regcache), + regno)); + + if (!n) + { + /* Invalidate the value of regcache that was set in function + "regcache_raw_write". */ + if (regno < 0) + { + int i; + for (i = 0; + i < gdbarch_num_regs (get_regcache_arch (regcache)); + i++) + regcache_invalidate (regcache, i); + } + else + regcache_invalidate (regcache, regno); + + error (_("Process record canceled the operation.")); + } + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + record_registers_change (regcache, regno); + } + record_beneath_to_store_registers (regcache, regno); +} + +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. + In replay mode, we cannot write memory unles we are willing to + invalidate the record/replay log from this point forward. */ + +static LONGEST +record_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte * readbuf, + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) +{ + if (!record_gdb_operation_disable + && (object == TARGET_OBJECT_MEMORY + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + { + if (RECORD_IS_REPLAY) + { + /* Let user choose if he wants to write memory or not. */ + if (!nquery (_("Because GDB is in replay mode, writing to memory " + "will make the execution log unusable from this " + "point onward. Write memory at address 0x%s?"), + paddr_nz (offset))) + return -1; + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + /* Check record_insn_num */ + record_check_insn_num (0); + + /* Record registers change to list as an instruction. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + if (record_arch_list_add_mem (offset, len)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; + } + + return record_beneath_to_xfer_partial (find_target_beneath (ops), object, + annex, readbuf, writebuf, offset, + len); +} + +/* record_insert_breakpoint + record_remove_breakpoint + Behavior is conditional on RECORD_IS_REPLAY. + We will not actually insert or remove breakpoints when replaying, + nor when recording. */ + +static int +record_insert_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_insert_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_remove_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_remove_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_can_execute_reverse (void) +{ + return 1; +} + +static void +init_record_ops (void) +{ + record_ops.to_shortname = "record"; + record_ops.to_longname = "Process record and replay target"; + record_ops.to_doc = + "Log program while executing and replay execution from log."; + record_ops.to_open = record_open; + record_ops.to_close = record_close; + record_ops.to_resume = record_resume; + record_ops.to_wait = record_wait; + record_ops.to_disconnect = record_disconnect; + record_ops.to_detach = record_detach; + record_ops.to_mourn_inferior = record_mourn_inferior; + record_ops.to_kill = record_kill; + record_ops.to_create_inferior = find_default_create_inferior; + record_ops.to_store_registers = record_store_registers; + record_ops.to_xfer_partial = record_xfer_partial; + record_ops.to_insert_breakpoint = record_insert_breakpoint; + record_ops.to_remove_breakpoint = record_remove_breakpoint; + record_ops.to_can_execute_reverse = record_can_execute_reverse; + record_ops.to_stratum = record_stratum; + record_ops.to_magic = OPS_MAGIC; +} + +static void +show_record_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), + value); +} + +/* cmd_record_start -- alias for "target record". */ + +static void +cmd_record_start (char *args, int from_tty) +{ + execute_command ("target record", from_tty); +} + +/* cmd_record_delete -- truncate the record log from the present point + of replay until the end. */ + +static void +cmd_record_delete (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (RECORD_IS_REPLAY) + { + if (!from_tty || query (_("Delete the log from this point forward " + "and begin to record the running message " + "at current PC?"))) + record_list_release_next (); + } + else + printf_unfiltered (_("Already at end of record list.\n")); + + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* cmd_record_stop -- implement the "stoprecord" command. */ + +static void +cmd_record_stop (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (!record_list || !from_tty || query (_("Delete recorded log and " + "stop recording?"))) + unpush_target (&record_ops); + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* set_record_insn_max_num -- set upper limit of record log size. */ + +static void +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) +{ + if (record_insn_num > record_insn_max_num && record_insn_max_num) + { + printf_unfiltered (_("Record instructions number is bigger than " + "record instructions max number. Auto delete " + "the first ones?\n")); + + while (record_insn_num > record_insn_max_num) + record_list_release_first (); + } +} + +/* show_record_insn_number -- print the current index + into the record log (number of insns recorded so far). */ + +static void +show_record_insn_number (char *ignore, int from_tty) +{ + printf_unfiltered (_("Record instruction number is %d.\n"), + record_insn_num); +} + +void +_initialize_record (void) +{ + /* Init record_maskall. */ + if (sigfillset (&record_maskall) == -1) + perror_with_name (_("Process record: sigfillset failed")); + + /* Init record_first. */ + record_first.prev = NULL; + record_first.next = NULL; + record_first.type = record_end; + record_first.u.need_dasm = 0; + + init_record_ops (); + add_target (&record_ops); + + add_setshow_zinteger_cmd ("record", no_class, &record_debug, + _("Set debugging of record/replay feature."), + _("Show debugging of record/replay feature."), + _("When enabled, debugging output for " + "record/replay feature is displayed."), + NULL, show_record_debug, &setdebuglist, + &showdebuglist); + + add_com ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command.")); + + add_com_alias ("rec", "record", class_obscure, 1); + + /* XXX: I try to use some simple commands such as "disconnect" and + "detach" to support this functions. But these commands all have + other affect to GDB such as call function "no_shared_libraries". + So I add special commands to GDB. */ + add_com ("delrecord", class_obscure, cmd_record_delete, + _("Delete the rest of execution log and start recording it anew.")); + add_com_alias ("dr", "delrecord", class_obscure, 1); + add_com ("stoprecord", class_obscure, cmd_record_stop, + _("Stop the record/replay target.")); + add_com_alias ("sr", "stoprecord", class_obscure, 1); + + /* Record instructions number limit command. */ + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, + &record_stop_at_limit, + _("Set whether record/replay stop when " + "record/replay buffer becomes full."), + _("Show whether record/replay stop when " + "record/replay buffer becomes full."), + _("Enable is default value.\n" + "When enabled, if the record/replay buffer " + "becomes full,\n" + "ask user what to do.\n" + "When disabled, if the record/replay buffer " + "becomes full,\n" + "delete it and start new recording."), + NULL, NULL, &setlist, &showlist); + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, + &record_insn_max_num, + _("Set record/replay buffer limit."), + _("Show record/replay buffer limit."), + _("Set the maximum number of instructions to be " + "stored in the\n" + "record/replay buffer. " + "Zero means unlimited (default 200000)."), + set_record_insn_max_num, + NULL, &setlist, &showlist); + add_info ("record-insn-number", show_record_insn_number, + _("Show the current number of instructions in the " + "record/replay buffer.")); +} --- /dev/null +++ b/record.h @@ -0,0 +1,87 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 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/>. */ + +#ifndef _RECORD_H_ +#define _RECORD_H_ + +#define TARGET_IS_PROCESS_RECORD \ + (current_target.beneath == &record_ops) +#define RECORD_IS_REPLAY \ + (record_list->next || execution_direction == EXEC_REVERSE) + +typedef struct record_reg_s +{ + int num; + gdb_byte *val; +} record_reg_t; + +typedef struct record_mem_s +{ + CORE_ADDR addr; + int len; + gdb_byte *val; +} record_mem_t; + +enum record_type +{ + record_end = 0, + record_reg, + record_mem +}; + +/* This is the core struct of record function. + + An entity of record_t is a record of the value change of a register + ("record_reg") or a part of memory ("record_mem"). And each + instruction must has a record_t ("record_end") that points out this + is the last record_t of this instruction. + + Each record_t is linked to "record_list" by "prev" and "next". + */ +typedef struct record_s +{ + struct record_s *prev; + struct record_s *next; + enum record_type type; + union + { + /* reg */ + record_reg_t reg; + /* mem */ + record_mem_t mem; + /* end */ + int need_dasm; + } u; +} record_t; + +extern int record_debug; +extern record_t *record_list; +extern record_t *record_arch_list_head; +extern record_t *record_arch_list_tail; +extern struct regcache *record_regcache; + +extern struct target_ops record_ops; + +extern int record_arch_list_add_reg (int num); +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); +extern int record_arch_list_add_end (int need_dasm); +extern void record_message (struct gdbarch *gdbarch); +extern struct cleanup * record_gdb_operation_disable_set (void); + +#endif /* _RECORD_H_ */ ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-02-17 7:12 ` teawater @ 2009-02-17 7:21 ` teawater 2009-02-23 16:05 ` teawater 2009-02-23 14:08 ` teawater 1 sibling, 1 reply; 38+ messages in thread From: teawater @ 2009-02-17 7:21 UTC (permalink / raw) To: Pedro Alves, Marc Khouzam; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 659 bytes --] Hi Pedro and Marc, I think find each new patches for prec must is a hard thing. So I post a bz2 package for it. Btw, 4-target_record_beneath.txt is delete from precord. Thanks, Hui On Tue, Feb 17, 2009 at 15:01, teawater <teawater@gmail.com> wrote: > Update follow the cvs-head. It just has a very small change - add a 0 > to be the argument of observer_notify_normal_stop. > > On Sun, Feb 8, 2009 at 21:03, teawater <teawater@gmail.com> wrote: >> Hi Pedro, >> >> According to your mail "Get rid of linux-thread-db.c:target_beneath". >> I get ride of this global beneath function pointers. >> >> Do you think this patch is OK? >> >> Thanks, >> Hui >> > [-- Attachment #2: prec.tar.bz2 --] [-- Type: application/x-bzip2, Size: 24873 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-02-17 7:21 ` teawater @ 2009-02-23 16:05 ` teawater 2009-03-03 20:40 ` Pedro Alves 2009-03-09 20:35 ` Pedro Alves 0 siblings, 2 replies; 38+ messages in thread From: teawater @ 2009-02-23 16:05 UTC (permalink / raw) To: Pedro Alves, Marc Khouzam; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 835 bytes --] Hi, This is the new version patches that follow cvs-head. Thanks, Hui On Tue, Feb 17, 2009 at 15:12, teawater <teawater@gmail.com> wrote: > Hi Pedro and Marc, > > I think find each new patches for prec must is a hard thing. So I > post a bz2 package for it. > > Btw, 4-target_record_beneath.txt is delete from precord. > > Thanks, > Hui > > On Tue, Feb 17, 2009 at 15:01, teawater <teawater@gmail.com> wrote: >> Update follow the cvs-head. It just has a very small change - add a 0 >> to be the argument of observer_notify_normal_stop. >> >> On Sun, Feb 8, 2009 at 21:03, teawater <teawater@gmail.com> wrote: >>> Hi Pedro, >>> >>> According to your mail "Get rid of linux-thread-db.c:target_beneath". >>> I get ride of this global beneath function pointers. >>> >>> Do you think this patch is OK? >>> >>> Thanks, >>> Hui >>> >> > [-- Attachment #2: precord2.tar.bz2 --] [-- Type: application/x-bzip2, Size: 24924 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-02-23 16:05 ` teawater @ 2009-03-03 20:40 ` Pedro Alves 2009-03-04 3:42 ` teawater 2009-03-09 20:35 ` Pedro Alves 1 sibling, 1 reply; 38+ messages in thread From: Pedro Alves @ 2009-03-03 20:40 UTC (permalink / raw) To: gdb-patches; +Cc: teawater, Marc Khouzam Hi Hui, Sorry for the delay in getting back to this. On Monday 23 February 2009 09:20:13, teawater wrote: > --- > gdbarch.sh | 4 ++++ > 1 file changed, 4 insertions(+) > > --- a/gdbarch.sh > +++ b/gdbarch.sh > @@ -709,6 +709,10 @@ F:char *:static_transform_name:char *nam > # Set if the address in N_SO or N_FUN stabs may be zero. > v:int:sofun_address_maybe_missing:::0:0::0 > > +# For the process record and replay target. > +M:int:process_record:CORE_ADDR addr:addr You'll need to extend this comment a little further. What is this callback really for? E.g., what is it supposed to do? These things should be documented here. About the interface itself, would it be possible to adjust the interface to make this callback's implementations not call record.c functions, but instead have record.c work only with the results of this callback? > +M:void:process_record_dasm:void > + I'm puzzled by this one. What's this for? I can't see it being used anywhere, did I miss something? What's "dasm"? If its not used for anything yet, let's remove it for now. -- Pedro Alves ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-03 20:40 ` Pedro Alves @ 2009-03-04 3:42 ` teawater 2009-03-09 6:01 ` teawater 2009-03-09 19:31 ` Pedro Alves 0 siblings, 2 replies; 38+ messages in thread From: teawater @ 2009-03-04 3:42 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Marc Khouzam Thanks Pedro, To get your mail is great for me. :) On Wed, Mar 4, 2009 at 04:39, Pedro Alves <pedro@codesourcery.com> wrote: > Hi Hui, > > Sorry for the delay in getting back to this. > > On Monday 23 February 2009 09:20:13, teawater wrote: > >> --- >> gdbarch.sh | 4 ++++ >> 1 file changed, 4 insertions(+) >> >> --- a/gdbarch.sh >> +++ b/gdbarch.sh >> @@ -709,6 +709,10 @@ F:char *:static_transform_name:char *nam >> # Set if the address in N_SO or N_FUN stabs may be zero. >> v:int:sofun_address_maybe_missing:::0:0::0 >> >> +# For the process record and replay target. >> +M:int:process_record:CORE_ADDR addr:addr > > You'll need to extend this comment a little further. What is this > callback really for? E.g., what is it supposed to do? These things > should be documented here. What about the following: # Record a execution log of instruction at address addr. > > About the interface itself, would it be possible to adjust the > interface to make this callback's implementations not call record.c > functions, but instead have record.c work only with the results of > this callback? Are you mean i386_process_record doesn't call the function in record.c? That is so hard. A lot of this record is same for each arch. So I encapsulation them to be some function. For example, record_arch_list_add_reg and record_arch_list_add_mem. Another arch will need it in the future. So, do you think it's ok? > >> +M:void:process_record_dasm:void >> + > > I'm puzzled by this one. What's this for? I can't see it being > used anywhere, did I miss something? What's "dasm"? If its not > used for anything yet, let's remove it for now. > In replay mode, gdb will call gdbarch_process_record_dasm to let arch special code analyzes the current instruction and do some replay job. It will make record speed up and decrease the memory use. It just support by mips arch, but mips precord code is removed now. So I will removed it and add it back when some arch support it. By the way, process_record_dasm is so ugly name. Do you have some idea with it? Thanks, Hui ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-04 3:42 ` teawater @ 2009-03-09 6:01 ` teawater 2009-03-09 19:31 ` Pedro Alves 1 sibling, 0 replies; 38+ messages in thread From: teawater @ 2009-03-09 6:01 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Marc Khouzam Hi Pedro, Sorry to disturb you. Could you please help me review it? Thanks, Hui On Wed, Mar 4, 2009 at 11:41, teawater <teawater@gmail.com> wrote: > > Thanks Pedro, > > To get your mail is great for me. :) > > On Wed, Mar 4, 2009 at 04:39, Pedro Alves <pedro@codesourcery.com> wrote: > > Hi Hui, > > > > Sorry for the delay in getting back to this. > > > > On Monday 23 February 2009 09:20:13, teawater wrote: > > > >> --- > >> gdbarch.sh | 4 ++++ > >> 1 file changed, 4 insertions(+) > >> > >> --- a/gdbarch.sh > >> +++ b/gdbarch.sh > >> @@ -709,6 +709,10 @@ F:char *:static_transform_name:char *nam > >> # Set if the address in N_SO or N_FUN stabs may be zero. > >> v:int:sofun_address_maybe_missing:::0:0::0 > >> > >> +# For the process record and replay target. > >> +M:int:process_record:CORE_ADDR addr:addr > > > > You'll need to extend this comment a little further. What is this > > callback really for? E.g., what is it supposed to do? These things > > should be documented here. > > What about the following: > # Record a execution log of instruction at address addr. > > > > > > About the interface itself, would it be possible to adjust the > > interface to make this callback's implementations not call record.c > > functions, but instead have record.c work only with the results of > > this callback? > > Are you mean i386_process_record doesn't call the function in record.c? > That is so hard. A lot of this record is same for each arch. So I > encapsulation them to be some function. > For example, record_arch_list_add_reg and record_arch_list_add_mem. > Another arch will need it in the future. > So, do you think it's ok? > > > > > >> +M:void:process_record_dasm:void > >> + > > > > I'm puzzled by this one. What's this for? I can't see it being > > used anywhere, did I miss something? What's "dasm"? If its not > > used for anything yet, let's remove it for now. > > > In replay mode, gdb will call gdbarch_process_record_dasm to let arch > special code analyzes the current instruction and do some replay job. > It will make record speed up and decrease the memory use. > > It just support by mips arch, but mips precord code is removed now. > So I will removed it and add it back when some arch support it. > > By the way, process_record_dasm is so ugly name. Do you have some idea with it? > > > Thanks, > Hui ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-04 3:42 ` teawater 2009-03-09 6:01 ` teawater @ 2009-03-09 19:31 ` Pedro Alves 2009-03-10 17:03 ` teawater 1 sibling, 1 reply; 38+ messages in thread From: Pedro Alves @ 2009-03-09 19:31 UTC (permalink / raw) To: gdb-patches; +Cc: teawater, Marc Khouzam On Wednesday 04 March 2009 03:41:59, teawater wrote: > >> +# For the process record and replay target. > >> +M:int:process_record:CORE_ADDR addr:addr > > > > You'll need to extend this comment a little further. What is this > > callback really for? E.g., what is it supposed to do? These things > > should be documented here. > > What about the following: > # Record a execution log of instruction at address addr. How about something more descriptive like: /* Parse the instruction at ADDR storing in the record execution log the registers and memory ranges that will be affected when the instruction executes, along with their current values. Return -1 if something goes wrong, 0 otherwise. */ > > About the interface itself, would it be possible to adjust the > > interface to make this callback's implementations not call record.c > > functions, but instead have record.c work only with the results of > > this callback? > > Are you mean i386_process_record doesn't call the function in record.c? Yes. > That is so hard. A lot of this record is same for each arch. So I > encapsulation them to be some function. > For example, record_arch_list_add_reg and record_arch_list_add_mem. > Another arch will need it in the future. > So, do you think it's ok? Let's keep it as is for now. Not much use in iterating over this detail. > >> +M:void:process_record_dasm:void > >> + > > > > I'm puzzled by this one. What's this for? I can't see it being > > used anywhere, did I miss something? What's "dasm"? If its not > > used for anything yet, let's remove it for now. > > > In replay mode, gdb will call gdbarch_process_record_dasm to let arch > special code analyzes the current instruction and do some replay job. > It will make record speed up and decrease the memory use. > > It just support by mips arch, but mips precord code is removed now. > So I will removed it and add it back when some arch support it. Thanks. > By the way, process_record_dasm is so ugly name. Do you have some idea with it? No, sorry, because I don't know what "dasm" means. -- Pedro Alves ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-09 19:31 ` Pedro Alves @ 2009-03-10 17:03 ` teawater 0 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-03-10 17:03 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Marc Khouzam Hi Pedro, On Tue, Mar 10, 2009 at 03:31, Pedro Alves <pedro@codesourcery.com> wrote: > On Wednesday 04 March 2009 03:41:59, teawater wrote: > >> >> +# For the process record and replay target. >> >> +M:int:process_record:CORE_ADDR addr:addr >> > >> > You'll need to extend this comment a little further. What is this >> > callback really for? E.g., what is it supposed to do? These things >> > should be documented here. >> >> What about the following: >> # Record a execution log of instruction at address addr. > > How about something more descriptive like: > > /* Parse the instruction at ADDR storing in the record execution log > the registers and memory ranges that will be affected when the > instruction executes, along with their current values. Return -1 > if something goes wrong, 0 otherwise. */ It's more better than mine. I will use it. > >> By the way, process_record_dasm is so ugly name. Do you have some idea with it? > > No, sorry, because I don't know what "dasm" means. Maybe delay it until I post a arch code that use it is better. :) Thanks for you help. Hui ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-02-23 16:05 ` teawater 2009-03-03 20:40 ` Pedro Alves @ 2009-03-09 20:35 ` Pedro Alves 2009-03-10 17:32 ` teawater ` (2 more replies) 1 sibling, 3 replies; 38+ messages in thread From: Pedro Alves @ 2009-03-09 20:35 UTC (permalink / raw) To: gdb-patches; +Cc: teawater, Marc Khouzam On Monday 23 February 2009 09:20:13, teawater wrote: > This is the new version patches that follow cvs-head. Thanks. > > gdb/Makefile.in | 4 > gdb/record.c | 1283 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > gdb/record.h | 87 +++ > 3 files changed, 1372 insertions(+), 2 deletions(-) > > Index: src/gdb/Makefile.in > =================================================================== > --- src.orig/gdb/Makefile.in 2009-02-21 15:53:27.000000000 +0000 > +++ src/gdb/Makefile.in 2009-02-28 20:23:20.000000000 +0000 > @@ -662,7 +662,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr > valarith.c valops.c valprint.c value.c varobj.c vec.c \ > wrapper.c \ > xml-tdesc.c xml-support.c \ > - inferior.c > + inferior.c record.c > > LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c > > @@ -813,7 +813,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ > solib.o solib-null.o \ > prologue-value.o memory-map.o xml-support.o \ > target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ > - inferior.o osdata.o > + inferior.o osdata.o record.o > > TSOBS = inflow.o > > Index: src/gdb/record.c > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ src/gdb/record.c 2009-02-28 20:23:20.000000000 +0000 > @@ -0,0 +1,1283 @@ > +/* Process record and replay target for GDB, the GNU debugger. > + > + Copyright (C) 2008 Free Software Foundation, Inc. Oops, you'll have to update this. > + > + This file is part of GDB. > + > + This program is free software; you can redistribute it and/or modify > + it under the terms of the GNU General Public License as published by > + the Free Software Foundation; either version 3 of the License, or > + (at your option) any later version. > + > + This program is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + GNU General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with this program. If not, see <http://www.gnu.org/licenses/>. */ > + > +#include "defs.h" > +#include "target.h" > +#include "gdbcmd.h" > +#include "regcache.h" > +#include "inferior.h" > +#include "gdbthread.h" > +#include "event-top.h" > +#include "annotate.h" > +#include "observer.h" > +#include "record.h" Why did you need annotate.h? Can you check which headers are really needed here? > + > +#include <signal.h> > + > +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 > + > +int record_debug = 0; > + > +record_t record_first; > +record_t *record_list = &record_first; > +record_t *record_arch_list_head = NULL; > +record_t *record_arch_list_tail = NULL; > +struct regcache *record_regcache = NULL; > + > +/* 1 ask user. 0 auto delete the last record_t. */ ^ double-space please. > +static int record_stop_at_limit = 1; > +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; > +static int record_insn_num = 0; > + > +struct target_ops record_ops; > +static int record_resume_step = 0; > +static enum target_signal record_resume_siggnal; > +static int record_get_sig = 0; > +static sigset_t record_maskall; > +static int record_gdb_operation_disable = 0; > +int record_will_store_registers = 0; You've got a bunch of magic variables here. Could you add some comments explaining what they're for? > + > +extern struct bp_location *bp_location_chain; This is not right. You're accessing a breakpoints.c internal implementation detail. We need to come up with interfaces to replace this hack. > + > +/* The beneath function pointers. */ > +static struct target_ops *record_beneath_to_resume_ops = NULL; > +static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, > + enum target_signal) = NULL; > +static struct target_ops *record_beneath_to_wait_ops = NULL; > +static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, > + struct target_waitstatus *) = NULL; > +static struct target_ops *record_beneath_to_store_registers_ops = NULL; > +static void (*record_beneath_to_store_registers) (struct target_ops *, > + struct regcache *, > + int regno) = NULL; > +static struct target_ops *record_beneath_to_xfer_partial_ops = NULL; > +static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, > + enum target_object object, > + const char *annex, > + gdb_byte * readbuf, > + const gdb_byte * writebuf, > + ULONGEST offset, > + LONGEST len) = NULL; > +static int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *) = > + NULL; > +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *) = > + NULL; I can't say I'm happy with this mechanism yet, but it is much better than the previous version of making target.c aware of record.c's callbacks. > + > +static void > +record_list_release (record_t * rec) ^ no space after *, please. > +{ > + record_t *tmp; > + > + if (!rec) > + return; > + > + while (rec->next) > + { > + rec = rec->next; > + } > + > + while (rec->prev) > + { > + tmp = rec; > + rec = rec->prev; > + if (tmp->type == record_reg) > + xfree (tmp->u.reg.val); > + else if (tmp->type == record_mem) > + xfree (tmp->u.mem.val); > + xfree (tmp); > + } > + > + if (rec != &record_first) > + xfree (rec); > +} > + > +static void > +record_list_release_next (void) > +{ > + record_t *rec = record_list; > + record_t *tmp = rec->next; > + rec->next = NULL; > + while (tmp) > + { > + rec = tmp->next; > + if (tmp->type == record_reg) > + record_insn_num--; > + else if (tmp->type == record_reg) > + xfree (tmp->u.reg.val); > + else if (tmp->type == record_mem) > + xfree (tmp->u.mem.val); > + xfree (tmp); > + tmp = rec; > + } > +} > + > +static void > +record_list_release_first (void) > +{ > + record_t *tmp = NULL; > + enum record_type type; > + > + if (!record_first.next) > + return; > + > + while (1) > + { > + type = record_first.next->type; > + > + if (type == record_reg) > + xfree (record_first.next->u.reg.val); > + else if (type == record_mem) > + xfree (record_first.next->u.mem.val); > + tmp = record_first.next; > + record_first.next = tmp->next; > + xfree (tmp); > + > + if (!record_first.next) > + { > + gdb_assert (record_insn_num == 1); > + break; > + } > + > + record_first.next->prev = &record_first; > + > + if (type == record_end) > + break; > + } > + > + record_insn_num--; > +} > + > +/* Add a record_t to record_arch_list. */ > +static void > +record_arch_list_add (record_t * rec) > +{ > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_arch_list_add %s.\n", > + host_address_to_string (rec)); > + > + if (record_arch_list_tail) > + { > + record_arch_list_tail->next = rec; > + rec->prev = record_arch_list_tail; > + record_arch_list_tail = rec; > + } > + else > + { > + record_arch_list_head = rec; > + record_arch_list_tail = rec; > + } > +} > + > +/* Record the value of a register ("num") to record_arch_list. */ When we want to mention the value of a parameter, we mentions it in uppercase, like so: /* Record the value of register NUM to record_arch_list. */ Also, there should be an empty line between the comment and the function itself. Here and elsewhere. > +int > +record_arch_list_add_reg (int num) > +{ > + record_t *rec; > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: add register num = %d to " > + "record list.\n", > + num); > + > + rec = (record_t *) xmalloc (sizeof (record_t)); > + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_reg; > + rec->u.reg.num = num; > + > + regcache_raw_read (record_regcache, num, rec->u.reg.val); > + > + record_arch_list_add (rec); > + > + return 0; > +} > + > +/* Record the value of a region of memory whose address is "addr" and > + length is "len" to record_arch_list. */ /* Record the value of a region of memory whose address is ADDR and length is LEN to record_arch_list. */ > + > +int > +record_arch_list_add_mem (CORE_ADDR addr, int len) > +{ > + record_t *rec; > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: add mem addr = 0x%s len = %d to " > + "record list.\n", > + paddr_nz (addr), len); > + > + if (!addr) > + return 0; Why is this check for addr == 0 necessary here? > + > + rec = (record_t *) xmalloc (sizeof (record_t)); > + rec->u.mem.val = (gdb_byte *) xmalloc (len); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_mem; > + rec->u.mem.addr = addr; > + rec->u.mem.len = len; > + > + if (target_read_memory (addr, rec->u.mem.val, len)) > + { > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: error reading memory at " > + "addr = 0x%s len = %d.\n", > + paddr_nz (addr), len); > + xfree (rec->u.mem.val); > + xfree (rec); > + return -1; > + } > + > + record_arch_list_add (rec); > + > + return 0; > +} > + > +/* Add a record_end type record_t to record_arch_list. */ > +int > +record_arch_list_add_end (int need_dasm) > +{ > + record_t *rec; > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: add end need_dasm = %d to " > + "arch list.\n", > + need_dasm); > + > + rec = (record_t *) xmalloc (sizeof (record_t)); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_end; > + > + rec->u.need_dasm = need_dasm; > + > + record_arch_list_add (rec); > + > + return 0; > +} > + > +static void > +record_check_insn_num (int set_terminal) > +{ > + if (record_insn_max_num) > + { > + gdb_assert (record_insn_num <= record_insn_max_num); > + if (record_insn_num == record_insn_max_num) > + { > + /* Ask user what to do. */ > + if (record_stop_at_limit) > + { > + int q; > + if (set_terminal) > + target_terminal_ours (); > + q = yquery (_("Do you want to auto delete previous execution " > + "log entries when record/replay buffer becomes " > + "full (record-stop-at-limit)?")); > + if (set_terminal) > + target_terminal_inferior (); > + if (q) > + record_stop_at_limit = 0; > + else > + error (_("Process record: inferior program stopped.")); > + } > + } > + } > +} > + > +static void > +record_normal_stop (void) > +{ > + finish_thread_state (minus_one_ptid); > + > + if (!breakpoints_always_inserted_mode () && target_has_execution) > + remove_breakpoints (); > + > + target_terminal_ours (); > + > + if (target_has_stack && !stop_stack_dummy) > + set_current_sal_from_frame (get_current_frame (), 1); > + > + select_frame (get_current_frame ()); > + > + annotate_stopped (); > + if (!suppress_stop_observer) > + { > + if (!ptid_equal (inferior_ptid, null_ptid)) > + observer_notify_normal_stop (inferior_thread ()->stop_bpstat, 0); > + else > + observer_notify_normal_stop (NULL, 0); > + } > +} Nope sorry, this is worse than before. When I asked to not call normal_stop, I didn't mean that you should inline chunks of it here. This is papering over a different problem. Why is it that record.c needs to do this, while other targets do not? If we need to do something like this, it should be needed by all targets that can throw from target_wait (which, is documented as something you shouldn't do anyway). Actually, do you still need any of this in current head? > + > +/* 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_list_release (record_arch_list_tail); > + set_executing (inferior_ptid, 0); > + record_normal_stop (); > +} Same as above. This isn't a record.c problem. > + > +void > +record_message (struct gdbarch *gdbarch) > +{ > + int ret; > + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); > + > + record_arch_list_head = NULL; > + record_arch_list_tail = NULL; > + > + /* Check record_insn_num. */ > + record_check_insn_num (1); > + > + record_regcache = get_current_regcache (); > + > + ret = gdbarch_process_record (gdbarch, > + regcache_read_pc (record_regcache)); > + if (ret > 0) > + error (_("Process record: inferior program stopped.")); > + if (ret < 0) > + error (_("Process record: failed to record execution log.")); > + > + discard_cleanups (old_cleanups); > + > + record_list->next = record_arch_list_head; > + record_arch_list_head->prev = record_list; > + record_list = record_arch_list_tail; > + > + if (record_insn_num == record_insn_max_num && record_insn_max_num) > + record_list_release_first (); > + else > + record_insn_num++; > +} > + > +/* Things to clean up if we error or QUIT out of function that set > + record_gdb_operation_disable (ie. command that caused target_wait to > + be called). */ > +static void > +record_gdb_operation_disable_cleanups (void *ignore) > +{ > + record_gdb_operation_disable = 0; > +} > + > +struct cleanup * > +record_gdb_operation_disable_set (void) > +{ > + struct cleanup *old_cleanups = > + make_cleanup (record_gdb_operation_disable_cleanups, 0); > + record_gdb_operation_disable = 1; > + > + return old_cleanups; > +} This is make_cleanup_restore_integer. > + > +static void > +record_open (char *name, int from_tty) > +{ > + struct target_ops *t; > + > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); > + > + /* check exec */ and core. > + if (!target_has_execution) > + error (_("Process record: the program is not being run.")); > + if (non_stop) > + error (_("Process record target can't debug inferior in non-stop mode " > + "(non-stop).")); > + if (target_async_permitted) > + error (_("Process record target can't debug inferior in asynchronous " > + "mode (target-async).")); > + > + if (!gdbarch_process_record_p (current_gdbarch)) > + error (_("Process record: the current architecture doesn't support " > + "record function.")); > + > + /* Check if record target is already running. */ > + if (TARGET_IS_PROCESS_RECORD) > + { > + if (!nquery > + (_("Process record target already running, do you want to delete " > + "the old record log?"))) > + return; > + } > + > + /* Set the beneath function pointers. */ > + for (t = current_target.beneath; t != NULL; t = t->beneath) > + { > + if (!record_beneath_to_resume) > + { > + record_beneath_to_resume = t->to_resume; > + record_beneath_to_resume_ops = t; > + } > + if (!record_beneath_to_wait) > + { > + record_beneath_to_wait = t->to_wait; > + record_beneath_to_wait_ops = t; > + } > + if (!record_beneath_to_store_registers) > + { > + record_beneath_to_store_registers = t->to_store_registers; > + record_beneath_to_store_registers_ops = t; > + } > + if (!record_beneath_to_xfer_partial) > + { > + record_beneath_to_xfer_partial = t->to_xfer_partial; > + record_beneath_to_xfer_partial_ops = t; > + } > + if (!record_beneath_to_insert_breakpoint) > + record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; > + if (!record_beneath_to_remove_breakpoint) > + record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; > + } > + if (!record_beneath_to_resume) > + error (_("Process record can't get to_resume.")); > + if (!record_beneath_to_wait) > + error (_("Process record can't get to_wait.")); > + if (!record_beneath_to_store_registers) > + error (_("Process record can't get to_store_registers.")); > + if (!record_beneath_to_xfer_partial) > + error (_("Process record can't get to_xfer_partial.")); > + if (!record_beneath_to_insert_breakpoint) > + error (_("Process record can't get to_insert_breakpoint.")); > + if (!record_beneath_to_remove_breakpoint) > + error (_("Process record can't get to_remove_breakpoint.")); As I said above, this is better than making target.c aware of these pointers, but, it still isn't ideal. For one thing, if some other layer/target is pushed after the record target is opened, these will be wrong. I would prefer that this beneath lookup would be done at each callback implementation (record_resume, etc.), but, I'm happy enough with this for now. It is now contained, so we can clean this up afterwards... > + > + push_target (&record_ops); > + > + /* Reset */ > + record_insn_num = 0; > + record_list = &record_first; > + record_list->next = NULL; > +} > + > +static void > +record_close (int quitting) > +{ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); > + > + record_list_release (record_list); > +} Shouldn't this clear the record_beneath_* pointers as well? > + > +static void > +record_resume (struct target_ops *ops, ptid_t ptid, int step, > + enum target_signal siggnal) > +{ > + record_resume_step = step; > + record_resume_siggnal = siggnal; > + > + if (!RECORD_IS_REPLAY) > + { > + record_message (current_gdbarch); > + record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, > + siggnal); > + } > +} > + > +static void > +record_sig_handler (int signo) > +{ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); > + > + record_resume_step = 1; > + record_get_sig = 1; > +} This handler is magical. Why is it setting resume_step, for instance? It would definitelly benefic from some comments. In fact, most of the file is undercommented. > + > +static void > +record_wait_cleanups (void *ignore) > +{ > + if (execution_direction == EXEC_REVERSE) > + { > + if (record_list->next) > + record_list = record_list->next; > + } > + else > + record_list = record_list->prev; > + > + set_executing (inferior_ptid, 0); > + record_normal_stop (); > +} See comments about record_normal_stop above. > + > +/* record_wait Please remove the function name from the comment. It's redundant. > + In replay mode, this function examines the recorded log and > + determines where to stop. */ > + > +static ptid_t > +record_wait (struct target_ops *ops, > + ptid_t ptid, struct target_waitstatus *status) > +{ > + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); > + > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_wait " > + "record_resume_step = %d\n", > + record_resume_step); > + > + if (!RECORD_IS_REPLAY) > + { > + if (record_resume_step) > + { > + /* This is a single step. */ > + return record_beneath_to_wait (record_beneath_to_wait_ops, > + ptid, status); > + } > + else > + { > + if (record_resume_step) > + { > + /* This is a single step. */ > + return record_beneath_to_wait (record_beneath_to_wait_ops, > + ptid, status); > + } > + else > + { > + /* This is not a single step. */ > + ptid_t ret; > + int is_breakpoint = 1; > + CORE_ADDR pc = 0; > + int pc_is_read = 0; > + struct bp_location *bl; > + struct breakpoint *b; > + > + do > + { > + ret = record_beneath_to_wait (record_beneath_to_wait_ops, > + ptid, status); > + > + if (status->kind == TARGET_WAITKIND_STOPPED > + && status->value.sig == TARGET_SIGNAL_TRAP) > + { > + /* Check if there is a breakpoint. */ > + pc_is_read = 0; > + registers_changed (); > + for (bl = bp_location_chain; bl; bl = bl->global_next) This will need to be fixed. Can you use the breakpoint_here-like functions exported by breakpoint.h instead of referencing bp_location_chain directly? > + { > + b = bl->owner; > + gdb_assert (b); > + if (b->enable_state != bp_enabled > + && b->enable_state != bp_permanent) > + continue; > + if (!pc_is_read) > + { > + pc = > + regcache_read_pc (get_thread_regcache (ret)); > + pc_is_read = 1; > + } > + switch (b->type) > + { > + default: > + if (bl->address == pc) > + goto breakpoint; > + break; > + > + case bp_watchpoint: > + /* XXX teawater: I still not very clear how to > + deal with it. */ > + goto breakpoint; > + break; > + > + case bp_catchpoint: > + gdb_assert (b->ops != NULL > + && b->ops->breakpoint_hit != NULL); > + if (b->ops->breakpoint_hit (b)) > + goto breakpoint; > + break; > + > + case bp_hardware_watchpoint: > + case bp_read_watchpoint: > + case bp_access_watchpoint: > + if (STOPPED_BY_WATCHPOINT (0)) > + goto breakpoint; > + break; > + } > + } > + > + /* There is not a breakpoint. */ > + record_message (current_gdbarch); > + record_beneath_to_resume (record_beneath_to_resume_ops, > + ptid, 1, > + record_resume_siggnal); > + continue; > + } > + > + is_breakpoint = 0; > + > + breakpoint: > + /* Add gdbarch_decr_pc_after_break to pc because gdb will > + expect the pc to be at address plus decr_pc_after_break > + when the inferior stops at a breakpoint. */ > + if (is_breakpoint) > + { > + CORE_ADDR decr_pc_after_break = > + gdbarch_decr_pc_after_break (current_gdbarch); > + if (decr_pc_after_break) > + { > + if (!pc_is_read) > + pc = > + regcache_read_pc (get_thread_regcache (ret)); > + regcache_write_pc (get_thread_regcache (ret), > + pc + decr_pc_after_break); > + } > + } > + > + break; > + } > + while (1); > + > + return ret; > + } > + } > + } > + else > + { > + int need_dasm = 0; > + struct regcache *regcache = get_current_regcache (); > + int continue_flag = 1; > + int first_record_end = 1; > + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); > + CORE_ADDR tmp_pc; > + > + status->kind = TARGET_WAITKIND_STOPPED; > + > + /* Check breakpoint when forward execute. */ > + if (execution_direction == EXEC_FORWARD) > + { > + tmp_pc = regcache_read_pc (regcache); > + if (breakpoint_inserted_here_p (tmp_pc)) > + { > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: break at 0x%s.\n", > + paddr_nz (tmp_pc)); > + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) > + && !record_resume_step) > + regcache_write_pc (regcache, > + tmp_pc + > + gdbarch_decr_pc_after_break > + (get_regcache_arch (regcache))); > + goto replay_out; > + } > + } > + > + record_get_sig = 0; > + signal (SIGINT, record_sig_handler); > + /* If GDB is in terminal_inferior mode, it will not get the signal. > + And in GDB replay mode, GDB doesn't need to be in terminal_inferior > + mode, because inferior will not executed. > + Then set it to terminal_ours to make GDB get the signal. */ > + target_terminal_ours (); > + > + /* In EXEC_FORWARD mode, record_list points to the tail of prev > + instruction. */ > + if (execution_direction == EXEC_FORWARD && record_list->next) > + record_list = record_list->next; > + > + /* Loop over the record_list, looking for the next place to > + stop. */ > + do > + { > + /* Check for beginning and end of log. */ > + if (execution_direction == EXEC_REVERSE > + && record_list == &record_first) > + { > + /* Hit beginning of record log in reverse. */ > + status->kind = TARGET_WAITKIND_NO_HISTORY; > + break; > + } > + if (execution_direction != EXEC_REVERSE && !record_list->next) > + { > + /* Hit end of record log going forward. */ > + status->kind = TARGET_WAITKIND_NO_HISTORY; > + break; > + } > + > + /* Set ptid, register and memory according to record_list. */ > + if (record_list->type == record_reg) > + { > + /* reg */ > + gdb_byte reg[MAX_REGISTER_SIZE]; > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_reg %s to " > + "inferior num = %d.\n", > + host_address_to_string (record_list), > + record_list->u.reg.num); > + regcache_cooked_read (regcache, record_list->u.reg.num, reg); > + regcache_cooked_write (regcache, record_list->u.reg.num, > + record_list->u.reg.val); > + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); > + } > + else if (record_list->type == record_mem) > + { > + /* mem */ > + gdb_byte *mem = alloca (record_list->u.mem.len); > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_mem %s to " > + "inferior addr = 0x%s len = %d.\n", > + host_address_to_string (record_list), > + paddr_nz (record_list->u.mem.addr), > + record_list->u.mem.len); > + > + if (target_read_memory > + (record_list->u.mem.addr, mem, record_list->u.mem.len)) > + error (_("Process record: error reading memory at " > + "addr = 0x%s len = %d."), > + paddr_nz (record_list->u.mem.addr), > + record_list->u.mem.len); > + > + if (target_write_memory > + (record_list->u.mem.addr, record_list->u.mem.val, > + record_list->u.mem.len)) > + error (_ > + ("Process record: error writing memory at " > + "addr = 0x%s len = %d."), > + paddr_nz (record_list->u.mem.addr), > + record_list->u.mem.len); > + > + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); > + } > + else > + { > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_end %s to " > + "inferior need_dasm = %d.\n", > + host_address_to_string (record_list), > + record_list->u.need_dasm); > + > + if (execution_direction == EXEC_FORWARD) > + need_dasm = record_list->u.need_dasm; > + if (need_dasm) > + gdbarch_process_record_dasm (current_gdbarch); > + > + if (first_record_end && execution_direction == EXEC_REVERSE) > + { > + /* When reverse excute, the first record_end is the part of > + current instruction. */ > + first_record_end = 0; > + } > + else > + { > + /* In EXEC_REVERSE mode, this is the record_end of prev > + instruction. > + In EXEC_FORWARD mode, this is the record_end of current > + instruction. */ > + /* step */ > + if (record_resume_step) > + { > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: step.\n"); > + continue_flag = 0; > + } > + > + /* check breakpoint */ > + tmp_pc = regcache_read_pc (regcache); > + if (breakpoint_inserted_here_p (tmp_pc)) > + { > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: break " > + "at 0x%s.\n", > + paddr_nz (tmp_pc)); > + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) > + && execution_direction == EXEC_FORWARD > + && !record_resume_step) > + regcache_write_pc (regcache, > + tmp_pc + > + gdbarch_decr_pc_after_break > + (get_regcache_arch (regcache))); > + continue_flag = 0; > + } > + } > + if (execution_direction == EXEC_REVERSE) > + need_dasm = record_list->u.need_dasm; > + } > + > +next: > + if (continue_flag) > + { > + if (execution_direction == EXEC_REVERSE) > + { > + if (record_list->prev) > + record_list = record_list->prev; > + } > + else > + { > + if (record_list->next) > + record_list = record_list->next; > + } > + } > + } > + while (continue_flag); > + > + signal (SIGINT, handle_sigint); > + > +replay_out: > + if (record_get_sig) > + status->value.sig = TARGET_SIGNAL_INT; > + else > + status->value.sig = TARGET_SIGNAL_TRAP; > + > + discard_cleanups (old_cleanups); > + } > + > + do_cleanups (set_cleanups); > + return inferior_ptid; > +} I have to say that I find that function confusing, due to the use of gotos, and excessive nesting. Personally, I much prefer code that does: if (foo) { /* something */ return; } if (bar) { /* something */ return; } if (lala) { /* something */ return; } Over: if (foo) { /* something */ return; } else { if (bar) { /* something */ return; } else { if (lala) { /* something */ return; } } } > + > +static void > +record_disconnect (struct target_ops *target, char *args, int from_tty) > +{ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); > + > + unpush_target (&record_ops); > + target_disconnect (args, from_tty); > +} > + > +static void > +record_detach (struct target_ops *ops, char *args, int from_tty) > +{ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); > + > + unpush_target (&record_ops); > + target_detach (args, from_tty); > +} This trick you're using happens to work, but, could you try this instead? Here and elsewhere similarly. struct target_ops *beneath = find_target_beaneath (ops); unpush_target (ops); beneath->to_detach (args, from_tty); > + > +static void > +record_mourn_inferior (struct target_ops *ops) > +{ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: " > + "record_mourn_inferior\n"); > + > + unpush_target (&record_ops); > + target_mourn_inferior (); > +} > + > +/* Close process record target before killing the inferior process. */ > +static void > +record_kill (void) > +{ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); > + > + unpush_target (&record_ops); > + target_kill (); > +} > + > +/* Record registers change (by user or by GDB) to list as an instruction. */ > +static void > +record_registers_change (struct regcache *regcache, int regnum) > +{ > + /* Check record_insn_num. */ > + record_check_insn_num (0); > + > + record_arch_list_head = NULL; > + record_arch_list_tail = NULL; > + > + record_regcache = get_current_regcache (); > + > + if (regnum < 0) > + { > + int i; > + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) > + { > + if (record_arch_list_add_reg (i)) > + { > + record_list_release (record_arch_list_tail); > + error (_("Process record: failed to record execution log.")); > + } > + } > + } > + else > + { > + if (record_arch_list_add_reg (regnum)) > + { > + record_list_release (record_arch_list_tail); > + error (_("Process record: failed to record execution log.")); > + } > + } > + if (record_arch_list_add_end (0)) > + { > + record_list_release (record_arch_list_tail); > + error (_("Process record: failed to record execution log.")); > + } > + record_list->next = record_arch_list_head; > + record_arch_list_head->prev = record_list; > + record_list = record_arch_list_tail; > + > + if (record_insn_num == record_insn_max_num && record_insn_max_num) > + record_list_release_first (); > + else > + record_insn_num++; > +} > + > +static void > +record_store_registers (struct target_ops *ops, struct regcache *regcache, > + int regno) > +{ > + if (!record_gdb_operation_disable) > + { > + if (RECORD_IS_REPLAY) > + { > + int n; > + struct cleanup *old_cleanups; > + > + /* Let user choose if he wants to write register or not. */ > + if (regno < 0) > + n = > + nquery (_("Because GDB is in replay mode, changing the " > + "value of a register will make the execution " > + "log unusable from this point onward. " > + "Change all registers?")); > + else > + n = > + nquery (_("Because GDB is in replay mode, changing the value " > + "of a register will make the execution log unusable " > + "from this point onward. Change register %s?"), > + gdbarch_register_name (get_regcache_arch (regcache), > + regno)); > + > + if (!n) > + { > + /* Invalidate the value of regcache that was set in function > + "regcache_raw_write". */ > + if (regno < 0) > + { > + int i; > + for (i = 0; > + i < gdbarch_num_regs (get_regcache_arch (regcache)); > + i++) > + regcache_invalidate (regcache, i); > + } > + else > + regcache_invalidate (regcache, regno); > + > + error (_("Process record canceled the operation.")); > + } > + > + /* Destroy the record from here forward. */ > + record_list_release_next (); > + } > + > + record_registers_change (regcache, regno); > + } > + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, > + regcache, regno); > +} > + > +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. > + In replay mode, we cannot write memory unles we are willing to > + invalidate the record/replay log from this point forward. */ > + > +static LONGEST > +record_xfer_partial (struct target_ops *ops, enum target_object object, > + const char *annex, gdb_byte * readbuf, > + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) > +{ > + if (!record_gdb_operation_disable > + && (object == TARGET_OBJECT_MEMORY > + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) > + { > + if (RECORD_IS_REPLAY) > + { > + /* Let user choose if he wants to write memory or not. */ > + if (!nquery (_("Because GDB is in replay mode, writing to memory " > + "will make the execution log unusable from this " > + "point onward. Write memory at address 0x%s?"), > + paddr_nz (offset))) > + return -1; > + > + /* Destroy the record from here forward. */ > + record_list_release_next (); > + } > + > + /* Check record_insn_num */ > + record_check_insn_num (0); > + > + /* Record registers change to list as an instruction. */ > + record_arch_list_head = NULL; > + record_arch_list_tail = NULL; > + if (record_arch_list_add_mem (offset, len)) > + { > + record_list_release (record_arch_list_tail); > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + _("Process record: failed to record " > + "execution log.")); > + return -1; > + } > + if (record_arch_list_add_end (0)) > + { > + record_list_release (record_arch_list_tail); > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + _("Process record: failed to record " > + "execution log.")); > + return -1; > + } > + record_list->next = record_arch_list_head; > + record_arch_list_head->prev = record_list; > + record_list = record_arch_list_tail; > + > + if (record_insn_num == record_insn_max_num && record_insn_max_num) > + record_list_release_first (); > + else > + record_insn_num++; > + } > + > + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, > + object, annex, readbuf, writebuf, > + offset, len); > +} > + > +/* record_insert_breakpoint > + record_remove_breakpoint > + Behavior is conditional on RECORD_IS_REPLAY. > + We will not actually insert or remove breakpoints when replaying, > + nor when recording. */ > + > +static int > +record_insert_breakpoint (struct bp_target_info *bp_tgt) > +{ > + if (!RECORD_IS_REPLAY) > + { > + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); > + int ret = record_beneath_to_insert_breakpoint (bp_tgt); > + > + do_cleanups (old_cleanups); > + > + return ret; > + } > + > + return 0; > +} > + > +static int > +record_remove_breakpoint (struct bp_target_info *bp_tgt) > +{ > + if (!RECORD_IS_REPLAY) > + { > + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); > + int ret = record_beneath_to_remove_breakpoint (bp_tgt); > + > + do_cleanups (old_cleanups); > + > + return ret; > + } > + > + return 0; > +} > + > +static int > +record_can_execute_reverse (void) > +{ > + return 1; > +} > + > +static void > +init_record_ops (void) > +{ > + record_ops.to_shortname = "record"; > + record_ops.to_longname = "Process record and replay target"; > + record_ops.to_doc = > + "Log program while executing and replay execution from log."; > + record_ops.to_open = record_open; > + record_ops.to_close = record_close; > + record_ops.to_resume = record_resume; > + record_ops.to_wait = record_wait; > + record_ops.to_disconnect = record_disconnect; > + record_ops.to_detach = record_detach; > + record_ops.to_mourn_inferior = record_mourn_inferior; > + record_ops.to_kill = record_kill; > + record_ops.to_create_inferior = find_default_create_inferior; > + record_ops.to_store_registers = record_store_registers; > + record_ops.to_xfer_partial = record_xfer_partial; > + record_ops.to_insert_breakpoint = record_insert_breakpoint; > + record_ops.to_remove_breakpoint = record_remove_breakpoint; > + record_ops.to_can_execute_reverse = record_can_execute_reverse; > + record_ops.to_stratum = record_stratum; > + record_ops.to_magic = OPS_MAGIC; > +} > + > +static void > +show_record_debug (struct ui_file *file, int from_tty, > + struct cmd_list_element *c, const char *value) > +{ > + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), > + value); > +} > + > +/* cmd_record_start -- alias for "target record". */ > + > +static void > +cmd_record_start (char *args, int from_tty) > +{ > + execute_command ("target record", from_tty); > +} > + > +/* cmd_record_delete -- truncate the record log from the present point > + of replay until the end. */ > + > +static void > +cmd_record_delete (char *args, int from_tty) > +{ > + if (TARGET_IS_PROCESS_RECORD) > + { > + if (RECORD_IS_REPLAY) > + { > + if (!from_tty || query (_("Delete the log from this point forward " > + "and begin to record the running message " > + "at current PC?"))) > + record_list_release_next (); > + } > + else > + printf_unfiltered (_("Already at end of record list.\n")); > + > + } > + else > + printf_unfiltered (_("Process record is not started.\n")); > +} > + > +/* cmd_record_stop -- implement the "stoprecord" command. */ > + > +static void > +cmd_record_stop (char *args, int from_tty) > +{ > + if (TARGET_IS_PROCESS_RECORD) > + { > + if (!record_list || !from_tty || query (_("Delete recorded log and " > + "stop recording?"))) > + unpush_target (&record_ops); > + } > + else > + printf_unfiltered (_("Process record is not started.\n")); > +} > + > +/* set_record_insn_max_num -- set upper limit of record log size. */ > + > +static void > +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) > +{ > + if (record_insn_num > record_insn_max_num && record_insn_max_num) > + { > + printf_unfiltered (_("Record instructions number is bigger than " > + "record instructions max number. Auto delete " > + "the first ones?\n")); > + > + while (record_insn_num > record_insn_max_num) > + record_list_release_first (); > + } > +} > + > +/* show_record_insn_number -- print the current index > + into the record log (number of insns recorded so far). */ > + > +static void > +show_record_insn_number (char *ignore, int from_tty) > +{ > + printf_unfiltered (_("Record instruction number is %d.\n"), > + record_insn_num); > +} > + > +void > +_initialize_record (void) > +{ > + /* Init record_maskall. */ > + if (sigfillset (&record_maskall) == -1) > + perror_with_name (_("Process record: sigfillset failed")); This will not build on all hosts. Is it still needed? I can't find any other reference to this variable in this patch. > + > + /* Init record_first. */ > + record_first.prev = NULL; > + record_first.next = NULL; > + record_first.type = record_end; > + record_first.u.need_dasm = 0; > + > + init_record_ops (); > + add_target (&record_ops); > + > + add_setshow_zinteger_cmd ("record", no_class, &record_debug, > + _("Set debugging of record/replay feature."), > + _("Show debugging of record/replay feature."), > + _("When enabled, debugging output for " > + "record/replay feature is displayed."), > + NULL, show_record_debug, &setdebuglist, > + &showdebuglist); > + > + add_com ("record", class_obscure, cmd_record_start, > + _("Abbreviated form of \"target record\" command.")); > + > + add_com_alias ("rec", "record", class_obscure, 1); > + > + /* XXX: I try to use some simple commands such as "disconnect" and > + "detach" to support this functions. But these commands all have > + other affect to GDB such as call function "no_shared_libraries". > + So I add special commands to GDB. */ > + add_com ("delrecord", class_obscure, cmd_record_delete, > + _("Delete the rest of execution log and start recording it anew.")); > + add_com_alias ("dr", "delrecord", class_obscure, 1); > + add_com ("stoprecord", class_obscure, cmd_record_stop, > + _("Stop the record/replay target.")); > + add_com_alias ("sr", "stoprecord", class_obscure, 1); > + > + /* Record instructions number limit command. */ > + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, > + &record_stop_at_limit, > + _("Set whether record/replay stop when " > + "record/replay buffer becomes full."), > + _("Show whether record/replay stop when " > + "record/replay buffer becomes full."), > + _("Enable is default value.\n" > + "When enabled, if the record/replay buffer " > + "becomes full,\n" > + "ask user what to do.\n" > + "When disabled, if the record/replay buffer " > + "becomes full,\n" > + "delete it and start new recording."), > + NULL, NULL, &setlist, &showlist); > + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, > + &record_insn_max_num, > + _("Set record/replay buffer limit."), > + _("Show record/replay buffer limit."), > + _("Set the maximum number of instructions to be " > + "stored in the\n" > + "record/replay buffer. " > + "Zero means unlimited (default 200000)."), > + set_record_insn_max_num, > + NULL, &setlist, &showlist); > + add_info ("record-insn-number", show_record_insn_number, > + _("Show the current number of instructions in the " > + "record/replay buffer.")); > +} > Index: src/gdb/record.h > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ src/gdb/record.h 2009-02-28 20:23:20.000000000 +0000 > @@ -0,0 +1,87 @@ > +/* Process record and replay target for GDB, the GNU debugger. > + > + Copyright (C) 2008 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/>. */ > + > +#ifndef _RECORD_H_ > +#define _RECORD_H_ > + > +#define TARGET_IS_PROCESS_RECORD \ > + (current_target.beneath == &record_ops) Sorry, but I repeat the request I've made several times already. This is not the right way to do this. You need to add a new target_ops method or property that the core of GDB checks on. It is not correct that make the core of GDB reference record_ops directly. To come up with the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, consider what is the property of the current target that GDB needs to know about the current target. Is it something like: target_is_recording () ? target_is_replaying () ? target_is_read_only () ? etc. > +#define RECORD_IS_REPLAY \ > + (record_list->next || execution_direction == EXEC_REVERSE) AFAICS, this macro is not used outside of record.c. It should move there, along with anything that isn't used outside of record.c. > + > +typedef struct record_reg_s > +{ > + int num; > + gdb_byte *val; > +} record_reg_t; > + > +typedef struct record_mem_s > +{ > + CORE_ADDR addr; > + int len; > + gdb_byte *val; > +} record_mem_t; > + > +enum record_type > +{ > + record_end = 0, > + record_reg, > + record_mem > +}; > + > +/* This is the core struct of record function. > + > + An entity of record_t is a record of the value change of a register > + ("record_reg") or a part of memory ("record_mem"). And each > + instruction must has a record_t ("record_end") that points out this > + is the last record_t of this instruction. > + > + Each record_t is linked to "record_list" by "prev" and "next". > + */ > +typedef struct record_s > +{ > + struct record_s *prev; > + struct record_s *next; > + enum record_type type; > + union > + { > + /* reg */ > + record_reg_t reg; > + /* mem */ > + record_mem_t mem; > + /* end */ > + int need_dasm; > + } u; > +} record_t; > + > +extern int record_debug; > +extern record_t *record_list; > +extern record_t *record_arch_list_head; > +extern record_t *record_arch_list_tail; > +extern struct regcache *record_regcache; Most of these things don't appear to be used anywhere else other than in record.c. Can you remove these declarations from the public header, and make them static in record.c? > + > +extern struct target_ops record_ops; Once you get rid of TARGET_IS_PROCESS_RECORD this doesn't need to be public anymore. > + > +extern int record_arch_list_add_reg (int num); > +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); > +extern int record_arch_list_add_end (int need_dasm); > +extern void record_message (struct gdbarch *gdbarch); > +extern struct cleanup * record_gdb_operation_disable_set (void); > + > +#endif /* _RECORD_H_ */ > -- Pedro Alves ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-09 20:35 ` Pedro Alves @ 2009-03-10 17:32 ` teawater 2009-03-10 19:35 ` Pedro Alves 2009-03-18 13:05 ` teawater 2009-03-18 13:14 ` teawater 2 siblings, 1 reply; 38+ messages in thread From: teawater @ 2009-03-10 17:32 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Michael Snyder Hi Pedro, On Tue, Mar 10, 2009 at 04:34, Pedro Alves <pedro@codesourcery.com> wrote: > On Monday 23 February 2009 09:20:13, teawater wrote: >> This is the new version patches that follow cvs-head. > > Thanks. > >> >> gdb/Makefile.in | 4 >> gdb/record.c | 1283 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ >> gdb/record.h | 87 +++ >> 3 files changed, 1372 insertions(+), 2 deletions(-) >> >> Index: src/gdb/Makefile.in >> =================================================================== >> --- src.orig/gdb/Makefile.in 2009-02-21 15:53:27.000000000 +0000 >> +++ src/gdb/Makefile.in 2009-02-28 20:23:20.000000000 +0000 >> @@ -662,7 +662,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr >> valarith.c valops.c valprint.c value.c varobj.c vec.c \ >> wrapper.c \ >> xml-tdesc.c xml-support.c \ >> - inferior.c >> + inferior.c record.c >> >> LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c >> >> @@ -813,7 +813,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ >> solib.o solib-null.o \ >> prologue-value.o memory-map.o xml-support.o \ >> target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ >> - inferior.o osdata.o >> + inferior.o osdata.o record.o >> >> TSOBS = inflow.o >> >> Index: src/gdb/record.c >> =================================================================== >> --- /dev/null 1970-01-01 00:00:00.000000000 +0000 >> +++ src/gdb/record.c 2009-02-28 20:23:20.000000000 +0000 >> @@ -0,0 +1,1283 @@ >> +/* Process record and replay target for GDB, the GNU debugger. >> + >> + Copyright (C) 2008 Free Software Foundation, Inc. > > Oops, you'll have to update this. I will. :) > >> + >> + This file is part of GDB. >> + >> + This program is free software; you can redistribute it and/or modify >> + it under the terms of the GNU General Public License as published by >> + the Free Software Foundation; either version 3 of the License, or >> + (at your option) any later version. >> + >> + This program is distributed in the hope that it will be useful, >> + but WITHOUT ANY WARRANTY; without even the implied warranty of >> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + GNU General Public License for more details. >> + >> + You should have received a copy of the GNU General Public License >> + along with this program. If not, see <http://www.gnu.org/licenses/>. */ >> + >> +#include "defs.h" >> +#include "target.h" >> +#include "gdbcmd.h" >> +#include "regcache.h" >> +#include "inferior.h" >> +#include "gdbthread.h" >> +#include "event-top.h" >> +#include "annotate.h" >> +#include "observer.h" >> +#include "record.h" > > Why did you need annotate.h? Can you check which headers are > really needed here? OK. I will check all of them. > >> + >> +#include <signal.h> >> + >> +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 >> + >> +int record_debug = 0; >> + >> +record_t record_first; >> +record_t *record_list = &record_first; >> +record_t *record_arch_list_head = NULL; >> +record_t *record_arch_list_tail = NULL; >> +struct regcache *record_regcache = NULL; >> + >> +/* 1 ask user. 0 auto delete the last record_t. */ > ^ double-space please. > >> +static int record_stop_at_limit = 1; >> +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; >> +static int record_insn_num = 0; >> + >> +struct target_ops record_ops; >> +static int record_resume_step = 0; >> +static enum target_signal record_resume_siggnal; >> +static int record_get_sig = 0; >> +static sigset_t record_maskall; >> +static int record_gdb_operation_disable = 0; >> +int record_will_store_registers = 0; > > You've got a bunch of magic variables here. Could you > add some comments explaining what they're for? OK. I will. > >> + >> +extern struct bp_location *bp_location_chain; > > This is not right. You're accessing a breakpoints.c > internal implementation detail. We need to come up with interfaces > to replace this hack. If I got a interface, I will remove it. > >> + >> +/* The beneath function pointers. */ >> +static struct target_ops *record_beneath_to_resume_ops = NULL; >> +static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, >> + enum target_signal) = NULL; >> +static struct target_ops *record_beneath_to_wait_ops = NULL; >> +static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, >> + struct target_waitstatus *) = NULL; >> +static struct target_ops *record_beneath_to_store_registers_ops = NULL; >> +static void (*record_beneath_to_store_registers) (struct target_ops *, >> + struct regcache *, >> + int regno) = NULL; >> +static struct target_ops *record_beneath_to_xfer_partial_ops = NULL; >> +static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, >> + enum target_object object, >> + const char *annex, >> + gdb_byte * readbuf, >> + const gdb_byte * writebuf, >> + ULONGEST offset, >> + LONGEST len) = NULL; >> +static int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *) = >> + NULL; >> +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *) = >> + NULL; > > I can't say I'm happy with this mechanism yet, but it is much > better than the previous version of making target.c aware of record.c's callbacks. > I had tried my best on it. > >> + >> +static void >> +record_list_release (record_t * rec) > > ^ no space after *, please. > >> +{ >> + record_t *tmp; >> + >> + if (!rec) >> + return; >> + >> + while (rec->next) >> + { >> + rec = rec->next; >> + } >> + >> + while (rec->prev) >> + { >> + tmp = rec; >> + rec = rec->prev; >> + if (tmp->type == record_reg) >> + xfree (tmp->u.reg.val); >> + else if (tmp->type == record_mem) >> + xfree (tmp->u.mem.val); >> + xfree (tmp); >> + } >> + >> + if (rec != &record_first) >> + xfree (rec); >> +} >> + >> +static void >> +record_list_release_next (void) >> +{ >> + record_t *rec = record_list; >> + record_t *tmp = rec->next; >> + rec->next = NULL; >> + while (tmp) >> + { >> + rec = tmp->next; >> + if (tmp->type == record_reg) >> + record_insn_num--; >> + else if (tmp->type == record_reg) >> + xfree (tmp->u.reg.val); >> + else if (tmp->type == record_mem) >> + xfree (tmp->u.mem.val); >> + xfree (tmp); >> + tmp = rec; >> + } >> +} >> + >> +static void >> +record_list_release_first (void) >> +{ >> + record_t *tmp = NULL; >> + enum record_type type; >> + >> + if (!record_first.next) >> + return; >> + >> + while (1) >> + { >> + type = record_first.next->type; >> + >> + if (type == record_reg) >> + xfree (record_first.next->u.reg.val); >> + else if (type == record_mem) >> + xfree (record_first.next->u.mem.val); >> + tmp = record_first.next; >> + record_first.next = tmp->next; >> + xfree (tmp); >> + >> + if (!record_first.next) >> + { >> + gdb_assert (record_insn_num == 1); >> + break; >> + } >> + >> + record_first.next->prev = &record_first; >> + >> + if (type == record_end) >> + break; >> + } >> + >> + record_insn_num--; >> +} >> + >> +/* Add a record_t to record_arch_list. */ >> +static void >> +record_arch_list_add (record_t * rec) >> +{ >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_arch_list_add %s.\n", >> + host_address_to_string (rec)); >> + >> + if (record_arch_list_tail) >> + { >> + record_arch_list_tail->next = rec; >> + rec->prev = record_arch_list_tail; >> + record_arch_list_tail = rec; >> + } >> + else >> + { >> + record_arch_list_head = rec; >> + record_arch_list_tail = rec; >> + } >> +} >> + >> +/* Record the value of a register ("num") to record_arch_list. */ > > When we want to mention the value of a parameter, we mentions it in > uppercase, like so: > > /* Record the value of register NUM to record_arch_list. */ > > Also, there should be an empty line between the comment and > the function itself. Here and elsewhere. > >> +int >> +record_arch_list_add_reg (int num) >> +{ >> + record_t *rec; >> + >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: add register num = %d to " >> + "record list.\n", >> + num); >> + >> + rec = (record_t *) xmalloc (sizeof (record_t)); >> + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_reg; >> + rec->u.reg.num = num; >> + >> + regcache_raw_read (record_regcache, num, rec->u.reg.val); >> + >> + record_arch_list_add (rec); >> + >> + return 0; >> +} >> + >> +/* Record the value of a region of memory whose address is "addr" and >> + length is "len" to record_arch_list. */ > > /* Record the value of a region of memory whose address is ADDR and > length is LEN to record_arch_list. */ > OK. I will fix all of them. >> + >> +int >> +record_arch_list_add_mem (CORE_ADDR addr, int len) >> +{ >> + record_t *rec; >> + >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: add mem addr = 0x%s len = %d to " >> + "record list.\n", >> + paddr_nz (addr), len); >> + > >> + if (!addr) >> + return 0; > > Why is this check for addr == 0 necessary here? Sometime, most time is system call. It will send a NULL address to Linux kernel. Process record will try to record the change of this address. So it need check if addr is 0. > >> + >> + rec = (record_t *) xmalloc (sizeof (record_t)); >> + rec->u.mem.val = (gdb_byte *) xmalloc (len); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_mem; >> + rec->u.mem.addr = addr; >> + rec->u.mem.len = len; >> + >> + if (target_read_memory (addr, rec->u.mem.val, len)) >> + { >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: error reading memory at " >> + "addr = 0x%s len = %d.\n", >> + paddr_nz (addr), len); >> + xfree (rec->u.mem.val); >> + xfree (rec); >> + return -1; >> + } >> + >> + record_arch_list_add (rec); >> + >> + return 0; >> +} >> + >> +/* Add a record_end type record_t to record_arch_list. */ >> +int >> +record_arch_list_add_end (int need_dasm) >> +{ >> + record_t *rec; >> + >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: add end need_dasm = %d to " >> + "arch list.\n", >> + need_dasm); >> + >> + rec = (record_t *) xmalloc (sizeof (record_t)); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_end; >> + >> + rec->u.need_dasm = need_dasm; >> + >> + record_arch_list_add (rec); >> + >> + return 0; >> +} >> + >> +static void >> +record_check_insn_num (int set_terminal) >> +{ >> + if (record_insn_max_num) >> + { >> + gdb_assert (record_insn_num <= record_insn_max_num); >> + if (record_insn_num == record_insn_max_num) >> + { >> + /* Ask user what to do. */ >> + if (record_stop_at_limit) >> + { >> + int q; >> + if (set_terminal) >> + target_terminal_ours (); >> + q = yquery (_("Do you want to auto delete previous execution " >> + "log entries when record/replay buffer becomes " >> + "full (record-stop-at-limit)?")); >> + if (set_terminal) >> + target_terminal_inferior (); >> + if (q) >> + record_stop_at_limit = 0; >> + else >> + error (_("Process record: inferior program stopped.")); >> + } >> + } >> + } >> +} >> + >> +static void >> +record_normal_stop (void) >> +{ >> + finish_thread_state (minus_one_ptid); >> + >> + if (!breakpoints_always_inserted_mode () && target_has_execution) >> + remove_breakpoints (); >> + >> + target_terminal_ours (); >> + >> + if (target_has_stack && !stop_stack_dummy) >> + set_current_sal_from_frame (get_current_frame (), 1); >> + >> + select_frame (get_current_frame ()); >> + >> + annotate_stopped (); >> + if (!suppress_stop_observer) >> + { >> + if (!ptid_equal (inferior_ptid, null_ptid)) >> + observer_notify_normal_stop (inferior_thread ()->stop_bpstat, 0); >> + else >> + observer_notify_normal_stop (NULL, 0); >> + } >> +} > > Nope sorry, this is worse than before. When I asked to not call > normal_stop, I didn't mean that you should inline chunks of > it here. This is papering over a different problem. Why > is it that record.c needs to do this, while other targets do not? > If we need to do something like this, it should be needed by all > targets that can throw from target_wait (which, is documented > as something you shouldn't do anyway). Actually, do you still need > any of this in current head? I will try to make record_message throw out error in record_wait. Then I can remove this normail_stop code. > >> + >> +/* 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_list_release (record_arch_list_tail); >> + set_executing (inferior_ptid, 0); >> + record_normal_stop (); >> +} > > Same as above. This isn't a record.c problem. > >> + >> +void >> +record_message (struct gdbarch *gdbarch) >> +{ >> + int ret; >> + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); >> + >> + record_arch_list_head = NULL; >> + record_arch_list_tail = NULL; >> + >> + /* Check record_insn_num. */ >> + record_check_insn_num (1); >> + >> + record_regcache = get_current_regcache (); >> + >> + ret = gdbarch_process_record (gdbarch, >> + regcache_read_pc (record_regcache)); >> + if (ret > 0) >> + error (_("Process record: inferior program stopped.")); >> + if (ret < 0) >> + error (_("Process record: failed to record execution log.")); >> + >> + discard_cleanups (old_cleanups); >> + >> + record_list->next = record_arch_list_head; >> + record_arch_list_head->prev = record_list; >> + record_list = record_arch_list_tail; >> + >> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >> + record_list_release_first (); >> + else >> + record_insn_num++; >> +} >> + > > >> +/* Things to clean up if we error or QUIT out of function that set >> + record_gdb_operation_disable (ie. command that caused target_wait to >> + be called). */ >> +static void >> +record_gdb_operation_disable_cleanups (void *ignore) >> +{ >> + record_gdb_operation_disable = 0; >> +} >> + >> +struct cleanup * >> +record_gdb_operation_disable_set (void) >> +{ >> + struct cleanup *old_cleanups = >> + make_cleanup (record_gdb_operation_disable_cleanups, 0); >> + record_gdb_operation_disable = 1; >> + >> + return old_cleanups; >> +} > > This is make_cleanup_restore_integer. I will change this code to use make_cleanup_restore_integer. > >> + >> +static void >> +record_open (char *name, int from_tty) >> +{ >> + struct target_ops *t; >> + >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); >> + >> + /* check exec */ > > and core. I will change it. > >> + if (!target_has_execution) >> + error (_("Process record: the program is not being run.")); >> + if (non_stop) >> + error (_("Process record target can't debug inferior in non-stop mode " >> + "(non-stop).")); >> + if (target_async_permitted) >> + error (_("Process record target can't debug inferior in asynchronous " >> + "mode (target-async).")); >> + >> + if (!gdbarch_process_record_p (current_gdbarch)) >> + error (_("Process record: the current architecture doesn't support " >> + "record function.")); >> + >> + /* Check if record target is already running. */ >> + if (TARGET_IS_PROCESS_RECORD) >> + { >> + if (!nquery >> + (_("Process record target already running, do you want to delete " >> + "the old record log?"))) >> + return; >> + } >> + >> + /* Set the beneath function pointers. */ >> + for (t = current_target.beneath; t != NULL; t = t->beneath) >> + { >> + if (!record_beneath_to_resume) >> + { >> + record_beneath_to_resume = t->to_resume; >> + record_beneath_to_resume_ops = t; >> + } >> + if (!record_beneath_to_wait) >> + { >> + record_beneath_to_wait = t->to_wait; >> + record_beneath_to_wait_ops = t; >> + } >> + if (!record_beneath_to_store_registers) >> + { >> + record_beneath_to_store_registers = t->to_store_registers; >> + record_beneath_to_store_registers_ops = t; >> + } >> + if (!record_beneath_to_xfer_partial) >> + { >> + record_beneath_to_xfer_partial = t->to_xfer_partial; >> + record_beneath_to_xfer_partial_ops = t; >> + } >> + if (!record_beneath_to_insert_breakpoint) >> + record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; >> + if (!record_beneath_to_remove_breakpoint) >> + record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; >> + } >> + if (!record_beneath_to_resume) >> + error (_("Process record can't get to_resume.")); >> + if (!record_beneath_to_wait) >> + error (_("Process record can't get to_wait.")); >> + if (!record_beneath_to_store_registers) >> + error (_("Process record can't get to_store_registers.")); >> + if (!record_beneath_to_xfer_partial) >> + error (_("Process record can't get to_xfer_partial.")); >> + if (!record_beneath_to_insert_breakpoint) >> + error (_("Process record can't get to_insert_breakpoint.")); >> + if (!record_beneath_to_remove_breakpoint) >> + error (_("Process record can't get to_remove_breakpoint.")); > > As I said above, this is better than making target.c aware of > these pointers, but, it still isn't ideal. For one thing, if > some other layer/target is pushed after the record target is opened, > these will be wrong. I would prefer that this beneath lookup > would be done at each callback implementation (record_resume, etc.), > but, I'm happy enough with this for now. It is now contained, so we can > clean this up afterwards... > >> + >> + push_target (&record_ops); >> + >> + /* Reset */ >> + record_insn_num = 0; >> + record_list = &record_first; >> + record_list->next = NULL; >> +} >> + >> +static void >> +record_close (int quitting) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); >> + >> + record_list_release (record_list); >> +} > > Shouldn't this clear the record_beneath_* pointers as well? Oops, I forget it. I will add a reset code to record_open. > >> + >> +static void >> +record_resume (struct target_ops *ops, ptid_t ptid, int step, >> + enum target_signal siggnal) >> +{ >> + record_resume_step = step; >> + record_resume_siggnal = siggnal; >> + >> + if (!RECORD_IS_REPLAY) >> + { >> + record_message (current_gdbarch); >> + record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, >> + siggnal); >> + } >> +} >> + >> +static void >> +record_sig_handler (int signo) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); >> + >> + record_resume_step = 1; >> + record_get_sig = 1; >> +} > > This handler is magical. Why is it setting resume_step, for instance? > It would definitelly benefic from some comments. In fact, most of the > file is undercommented. I will add comment to there. > >> + >> +static void >> +record_wait_cleanups (void *ignore) >> +{ >> + if (execution_direction == EXEC_REVERSE) >> + { >> + if (record_list->next) >> + record_list = record_list->next; >> + } >> + else >> + record_list = record_list->prev; >> + >> + set_executing (inferior_ptid, 0); >> + record_normal_stop (); >> +} > > See comments about record_normal_stop above. I will try it too. > >> + >> +/* record_wait > > Please remove the function name from the comment. It's redundant. > >> + In replay mode, this function examines the recorded log and >> + determines where to stop. */ OK. I will. >> + >> +static ptid_t >> +record_wait (struct target_ops *ops, >> + ptid_t ptid, struct target_waitstatus *status) >> +{ >> + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); >> + >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_wait " >> + "record_resume_step = %d\n", >> + record_resume_step); >> + >> + if (!RECORD_IS_REPLAY) >> + { >> + if (record_resume_step) >> + { >> + /* This is a single step. */ >> + return record_beneath_to_wait (record_beneath_to_wait_ops, >> + ptid, status); >> + } >> + else >> + { >> + if (record_resume_step) >> + { >> + /* This is a single step. */ >> + return record_beneath_to_wait (record_beneath_to_wait_ops, >> + ptid, status); >> + } >> + else >> + { >> + /* This is not a single step. */ >> + ptid_t ret; >> + int is_breakpoint = 1; >> + CORE_ADDR pc = 0; >> + int pc_is_read = 0; >> + struct bp_location *bl; >> + struct breakpoint *b; >> + >> + do >> + { >> + ret = record_beneath_to_wait (record_beneath_to_wait_ops, >> + ptid, status); >> + >> + if (status->kind == TARGET_WAITKIND_STOPPED >> + && status->value.sig == TARGET_SIGNAL_TRAP) >> + { >> + /* Check if there is a breakpoint. */ >> + pc_is_read = 0; >> + registers_changed (); >> + for (bl = bp_location_chain; bl; bl = bl->global_next) > > This will need to be fixed. Can you use the breakpoint_here-like functions > exported by breakpoint.h instead of referencing bp_location_chain directly? I will try to change it. > >> + { >> + b = bl->owner; >> + gdb_assert (b); >> + if (b->enable_state != bp_enabled >> + && b->enable_state != bp_permanent) >> + continue; >> + if (!pc_is_read) >> + { >> + pc = >> + regcache_read_pc (get_thread_regcache (ret)); >> + pc_is_read = 1; >> + } >> + switch (b->type) >> + { >> + default: >> + if (bl->address == pc) >> + goto breakpoint; >> + break; >> + >> + case bp_watchpoint: >> + /* XXX teawater: I still not very clear how to >> + deal with it. */ >> + goto breakpoint; >> + break; >> + >> + case bp_catchpoint: >> + gdb_assert (b->ops != NULL >> + && b->ops->breakpoint_hit != NULL); >> + if (b->ops->breakpoint_hit (b)) >> + goto breakpoint; >> + break; >> + >> + case bp_hardware_watchpoint: >> + case bp_read_watchpoint: >> + case bp_access_watchpoint: >> + if (STOPPED_BY_WATCHPOINT (0)) >> + goto breakpoint; >> + break; >> + } >> + } >> + >> + /* There is not a breakpoint. */ >> + record_message (current_gdbarch); >> + record_beneath_to_resume (record_beneath_to_resume_ops, >> + ptid, 1, >> + record_resume_siggnal); >> + continue; >> + } >> + >> + is_breakpoint = 0; >> + >> + breakpoint: >> + /* Add gdbarch_decr_pc_after_break to pc because gdb will >> + expect the pc to be at address plus decr_pc_after_break >> + when the inferior stops at a breakpoint. */ >> + if (is_breakpoint) >> + { >> + CORE_ADDR decr_pc_after_break = >> + gdbarch_decr_pc_after_break (current_gdbarch); >> + if (decr_pc_after_break) >> + { >> + if (!pc_is_read) >> + pc = >> + regcache_read_pc (get_thread_regcache (ret)); >> + regcache_write_pc (get_thread_regcache (ret), >> + pc + decr_pc_after_break); >> + } >> + } >> + >> + break; >> + } >> + while (1); >> + >> + return ret; >> + } >> + } >> + } >> + else >> + { >> + int need_dasm = 0; >> + struct regcache *regcache = get_current_regcache (); >> + int continue_flag = 1; >> + int first_record_end = 1; >> + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); >> + CORE_ADDR tmp_pc; >> + >> + status->kind = TARGET_WAITKIND_STOPPED; >> + >> + /* Check breakpoint when forward execute. */ >> + if (execution_direction == EXEC_FORWARD) >> + { >> + tmp_pc = regcache_read_pc (regcache); >> + if (breakpoint_inserted_here_p (tmp_pc)) >> + { >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: break at 0x%s.\n", >> + paddr_nz (tmp_pc)); >> + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) >> + && !record_resume_step) >> + regcache_write_pc (regcache, >> + tmp_pc + >> + gdbarch_decr_pc_after_break >> + (get_regcache_arch (regcache))); >> + goto replay_out; >> + } >> + } >> + >> + record_get_sig = 0; >> + signal (SIGINT, record_sig_handler); >> + /* If GDB is in terminal_inferior mode, it will not get the signal. >> + And in GDB replay mode, GDB doesn't need to be in terminal_inferior >> + mode, because inferior will not executed. >> + Then set it to terminal_ours to make GDB get the signal. */ >> + target_terminal_ours (); >> + >> + /* In EXEC_FORWARD mode, record_list points to the tail of prev >> + instruction. */ >> + if (execution_direction == EXEC_FORWARD && record_list->next) >> + record_list = record_list->next; >> + >> + /* Loop over the record_list, looking for the next place to >> + stop. */ >> + do >> + { >> + /* Check for beginning and end of log. */ >> + if (execution_direction == EXEC_REVERSE >> + && record_list == &record_first) >> + { >> + /* Hit beginning of record log in reverse. */ >> + status->kind = TARGET_WAITKIND_NO_HISTORY; >> + break; >> + } >> + if (execution_direction != EXEC_REVERSE && !record_list->next) >> + { >> + /* Hit end of record log going forward. */ >> + status->kind = TARGET_WAITKIND_NO_HISTORY; >> + break; >> + } >> + >> + /* Set ptid, register and memory according to record_list. */ >> + if (record_list->type == record_reg) >> + { >> + /* reg */ >> + gdb_byte reg[MAX_REGISTER_SIZE]; >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_reg %s to " >> + "inferior num = %d.\n", >> + host_address_to_string (record_list), >> + record_list->u.reg.num); >> + regcache_cooked_read (regcache, record_list->u.reg.num, reg); >> + regcache_cooked_write (regcache, record_list->u.reg.num, >> + record_list->u.reg.val); >> + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); >> + } >> + else if (record_list->type == record_mem) >> + { >> + /* mem */ >> + gdb_byte *mem = alloca (record_list->u.mem.len); >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_mem %s to " >> + "inferior addr = 0x%s len = %d.\n", >> + host_address_to_string (record_list), >> + paddr_nz (record_list->u.mem.addr), >> + record_list->u.mem.len); >> + >> + if (target_read_memory >> + (record_list->u.mem.addr, mem, record_list->u.mem.len)) >> + error (_("Process record: error reading memory at " >> + "addr = 0x%s len = %d."), >> + paddr_nz (record_list->u.mem.addr), >> + record_list->u.mem.len); >> + >> + if (target_write_memory >> + (record_list->u.mem.addr, record_list->u.mem.val, >> + record_list->u.mem.len)) >> + error (_ >> + ("Process record: error writing memory at " >> + "addr = 0x%s len = %d."), >> + paddr_nz (record_list->u.mem.addr), >> + record_list->u.mem.len); >> + >> + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); >> + } >> + else >> + { >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_end %s to " >> + "inferior need_dasm = %d.\n", >> + host_address_to_string (record_list), >> + record_list->u.need_dasm); >> + >> + if (execution_direction == EXEC_FORWARD) >> + need_dasm = record_list->u.need_dasm; >> + if (need_dasm) >> + gdbarch_process_record_dasm (current_gdbarch); >> + >> + if (first_record_end && execution_direction == EXEC_REVERSE) >> + { >> + /* When reverse excute, the first record_end is the part of >> + current instruction. */ >> + first_record_end = 0; >> + } >> + else >> + { >> + /* In EXEC_REVERSE mode, this is the record_end of prev >> + instruction. >> + In EXEC_FORWARD mode, this is the record_end of current >> + instruction. */ >> + /* step */ >> + if (record_resume_step) >> + { >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: step.\n"); >> + continue_flag = 0; >> + } >> + >> + /* check breakpoint */ >> + tmp_pc = regcache_read_pc (regcache); >> + if (breakpoint_inserted_here_p (tmp_pc)) >> + { >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: break " >> + "at 0x%s.\n", >> + paddr_nz (tmp_pc)); >> + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) >> + && execution_direction == EXEC_FORWARD >> + && !record_resume_step) >> + regcache_write_pc (regcache, >> + tmp_pc + >> + gdbarch_decr_pc_after_break >> + (get_regcache_arch (regcache))); >> + continue_flag = 0; >> + } >> + } >> + if (execution_direction == EXEC_REVERSE) >> + need_dasm = record_list->u.need_dasm; >> + } >> + >> +next: >> + if (continue_flag) >> + { >> + if (execution_direction == EXEC_REVERSE) >> + { >> + if (record_list->prev) >> + record_list = record_list->prev; >> + } >> + else >> + { >> + if (record_list->next) >> + record_list = record_list->next; >> + } >> + } >> + } >> + while (continue_flag); >> + >> + signal (SIGINT, handle_sigint); >> + >> +replay_out: >> + if (record_get_sig) >> + status->value.sig = TARGET_SIGNAL_INT; >> + else >> + status->value.sig = TARGET_SIGNAL_TRAP; >> + >> + discard_cleanups (old_cleanups); >> + } >> + >> + do_cleanups (set_cleanups); >> + return inferior_ptid; >> +} > > I have to say that I find that function confusing, due to > the use of gotos, and excessive nesting. Personally, I much prefer > code that does: > > if (foo) > { > /* something */ > return; > } > > if (bar) > { > /* something */ > return; > } > > if (lala) > { > /* something */ > return; > } > > Over: > > if (foo) > { > /* something */ > return; > } > else > { > if (bar) > { > /* something */ > return; > } > else > { > if (lala) > { > /* something */ > return; > } > } > } > In record_wait, all the code in else part's return need do same thing: signal (SIGINT, handle_sigint); if (record_get_sig) status->value.sig = TARGET_SIGNAL_INT; else status->value.sig = TARGET_SIGNAL_TRAP; discard_cleanups (old_cleanups); BTW, after remove a lot of part of this function, this function must be clear. :) > >> + >> +static void >> +record_disconnect (struct target_ops *target, char *args, int from_tty) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); >> + >> + unpush_target (&record_ops); >> + target_disconnect (args, from_tty); >> +} >> + >> +static void >> +record_detach (struct target_ops *ops, char *args, int from_tty) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); >> + >> + unpush_target (&record_ops); >> + target_detach (args, from_tty); >> +} > > This trick you're using happens to work, but, could you try > this instead? Here and elsewhere similarly. > > struct target_ops *beneath = find_target_beaneath (ops); > unpush_target (ops); > beneath->to_detach (args, from_tty); OK. I will. > >> + >> +static void >> +record_mourn_inferior (struct target_ops *ops) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: " >> + "record_mourn_inferior\n"); >> + >> + unpush_target (&record_ops); >> + target_mourn_inferior (); >> +} >> + >> +/* Close process record target before killing the inferior process. */ >> +static void >> +record_kill (void) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); >> + >> + unpush_target (&record_ops); >> + target_kill (); >> +} >> + >> +/* Record registers change (by user or by GDB) to list as an instruction. */ >> +static void >> +record_registers_change (struct regcache *regcache, int regnum) >> +{ >> + /* Check record_insn_num. */ >> + record_check_insn_num (0); >> + >> + record_arch_list_head = NULL; >> + record_arch_list_tail = NULL; >> + >> + record_regcache = get_current_regcache (); >> + >> + if (regnum < 0) >> + { >> + int i; >> + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) >> + { >> + if (record_arch_list_add_reg (i)) >> + { >> + record_list_release (record_arch_list_tail); >> + error (_("Process record: failed to record execution log.")); >> + } >> + } >> + } >> + else >> + { >> + if (record_arch_list_add_reg (regnum)) >> + { >> + record_list_release (record_arch_list_tail); >> + error (_("Process record: failed to record execution log.")); >> + } >> + } >> + if (record_arch_list_add_end (0)) >> + { >> + record_list_release (record_arch_list_tail); >> + error (_("Process record: failed to record execution log.")); >> + } >> + record_list->next = record_arch_list_head; >> + record_arch_list_head->prev = record_list; >> + record_list = record_arch_list_tail; >> + >> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >> + record_list_release_first (); >> + else >> + record_insn_num++; >> +} >> + >> +static void >> +record_store_registers (struct target_ops *ops, struct regcache *regcache, >> + int regno) >> +{ >> + if (!record_gdb_operation_disable) >> + { >> + if (RECORD_IS_REPLAY) >> + { >> + int n; >> + struct cleanup *old_cleanups; >> + >> + /* Let user choose if he wants to write register or not. */ >> + if (regno < 0) >> + n = >> + nquery (_("Because GDB is in replay mode, changing the " >> + "value of a register will make the execution " >> + "log unusable from this point onward. " >> + "Change all registers?")); >> + else >> + n = >> + nquery (_("Because GDB is in replay mode, changing the value " >> + "of a register will make the execution log unusable " >> + "from this point onward. Change register %s?"), >> + gdbarch_register_name (get_regcache_arch (regcache), >> + regno)); >> + >> + if (!n) >> + { >> + /* Invalidate the value of regcache that was set in function >> + "regcache_raw_write". */ >> + if (regno < 0) >> + { >> + int i; >> + for (i = 0; >> + i < gdbarch_num_regs (get_regcache_arch (regcache)); >> + i++) >> + regcache_invalidate (regcache, i); >> + } >> + else >> + regcache_invalidate (regcache, regno); >> + >> + error (_("Process record canceled the operation.")); >> + } >> + >> + /* Destroy the record from here forward. */ >> + record_list_release_next (); >> + } >> + >> + record_registers_change (regcache, regno); >> + } >> + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, >> + regcache, regno); >> +} >> + >> +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. >> + In replay mode, we cannot write memory unles we are willing to >> + invalidate the record/replay log from this point forward. */ >> + >> +static LONGEST >> +record_xfer_partial (struct target_ops *ops, enum target_object object, >> + const char *annex, gdb_byte * readbuf, >> + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) >> +{ >> + if (!record_gdb_operation_disable >> + && (object == TARGET_OBJECT_MEMORY >> + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) >> + { >> + if (RECORD_IS_REPLAY) >> + { >> + /* Let user choose if he wants to write memory or not. */ >> + if (!nquery (_("Because GDB is in replay mode, writing to memory " >> + "will make the execution log unusable from this " >> + "point onward. Write memory at address 0x%s?"), >> + paddr_nz (offset))) >> + return -1; >> + >> + /* Destroy the record from here forward. */ >> + record_list_release_next (); >> + } >> + >> + /* Check record_insn_num */ >> + record_check_insn_num (0); >> + >> + /* Record registers change to list as an instruction. */ >> + record_arch_list_head = NULL; >> + record_arch_list_tail = NULL; >> + if (record_arch_list_add_mem (offset, len)) >> + { >> + record_list_release (record_arch_list_tail); >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + _("Process record: failed to record " >> + "execution log.")); >> + return -1; >> + } >> + if (record_arch_list_add_end (0)) >> + { >> + record_list_release (record_arch_list_tail); >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + _("Process record: failed to record " >> + "execution log.")); >> + return -1; >> + } >> + record_list->next = record_arch_list_head; >> + record_arch_list_head->prev = record_list; >> + record_list = record_arch_list_tail; >> + >> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >> + record_list_release_first (); >> + else >> + record_insn_num++; >> + } >> + >> + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, >> + object, annex, readbuf, writebuf, >> + offset, len); >> +} >> + >> +/* record_insert_breakpoint >> + record_remove_breakpoint >> + Behavior is conditional on RECORD_IS_REPLAY. >> + We will not actually insert or remove breakpoints when replaying, >> + nor when recording. */ >> + >> +static int >> +record_insert_breakpoint (struct bp_target_info *bp_tgt) >> +{ >> + if (!RECORD_IS_REPLAY) >> + { >> + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); >> + int ret = record_beneath_to_insert_breakpoint (bp_tgt); >> + >> + do_cleanups (old_cleanups); >> + >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int >> +record_remove_breakpoint (struct bp_target_info *bp_tgt) >> +{ >> + if (!RECORD_IS_REPLAY) >> + { >> + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); >> + int ret = record_beneath_to_remove_breakpoint (bp_tgt); >> + >> + do_cleanups (old_cleanups); >> + >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int >> +record_can_execute_reverse (void) >> +{ >> + return 1; >> +} >> + >> +static void >> +init_record_ops (void) >> +{ >> + record_ops.to_shortname = "record"; >> + record_ops.to_longname = "Process record and replay target"; >> + record_ops.to_doc = >> + "Log program while executing and replay execution from log."; >> + record_ops.to_open = record_open; >> + record_ops.to_close = record_close; >> + record_ops.to_resume = record_resume; >> + record_ops.to_wait = record_wait; >> + record_ops.to_disconnect = record_disconnect; >> + record_ops.to_detach = record_detach; >> + record_ops.to_mourn_inferior = record_mourn_inferior; >> + record_ops.to_kill = record_kill; >> + record_ops.to_create_inferior = find_default_create_inferior; >> + record_ops.to_store_registers = record_store_registers; >> + record_ops.to_xfer_partial = record_xfer_partial; >> + record_ops.to_insert_breakpoint = record_insert_breakpoint; >> + record_ops.to_remove_breakpoint = record_remove_breakpoint; >> + record_ops.to_can_execute_reverse = record_can_execute_reverse; >> + record_ops.to_stratum = record_stratum; >> + record_ops.to_magic = OPS_MAGIC; >> +} >> + >> +static void >> +show_record_debug (struct ui_file *file, int from_tty, >> + struct cmd_list_element *c, const char *value) >> +{ >> + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), >> + value); >> +} >> + >> +/* cmd_record_start -- alias for "target record". */ >> + >> +static void >> +cmd_record_start (char *args, int from_tty) >> +{ >> + execute_command ("target record", from_tty); >> +} >> + >> +/* cmd_record_delete -- truncate the record log from the present point >> + of replay until the end. */ >> + >> +static void >> +cmd_record_delete (char *args, int from_tty) >> +{ >> + if (TARGET_IS_PROCESS_RECORD) >> + { >> + if (RECORD_IS_REPLAY) >> + { >> + if (!from_tty || query (_("Delete the log from this point forward " >> + "and begin to record the running message " >> + "at current PC?"))) >> + record_list_release_next (); >> + } >> + else >> + printf_unfiltered (_("Already at end of record list.\n")); >> + >> + } >> + else >> + printf_unfiltered (_("Process record is not started.\n")); >> +} >> + >> +/* cmd_record_stop -- implement the "stoprecord" command. */ >> + >> +static void >> +cmd_record_stop (char *args, int from_tty) >> +{ >> + if (TARGET_IS_PROCESS_RECORD) >> + { >> + if (!record_list || !from_tty || query (_("Delete recorded log and " >> + "stop recording?"))) >> + unpush_target (&record_ops); >> + } >> + else >> + printf_unfiltered (_("Process record is not started.\n")); >> +} >> + >> +/* set_record_insn_max_num -- set upper limit of record log size. */ >> + >> +static void >> +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) >> +{ >> + if (record_insn_num > record_insn_max_num && record_insn_max_num) >> + { >> + printf_unfiltered (_("Record instructions number is bigger than " >> + "record instructions max number. Auto delete " >> + "the first ones?\n")); >> + >> + while (record_insn_num > record_insn_max_num) >> + record_list_release_first (); >> + } >> +} >> + >> +/* show_record_insn_number -- print the current index >> + into the record log (number of insns recorded so far). */ >> + >> +static void >> +show_record_insn_number (char *ignore, int from_tty) >> +{ >> + printf_unfiltered (_("Record instruction number is %d.\n"), >> + record_insn_num); >> +} >> + >> +void >> +_initialize_record (void) >> +{ >> + /* Init record_maskall. */ >> + if (sigfillset (&record_maskall) == -1) >> + perror_with_name (_("Process record: sigfillset failed")); > > This will not build on all hosts. Is it still needed? I can't > find any other reference to this variable in this patch. I forget it. I will remove it. > >> + >> + /* Init record_first. */ >> + record_first.prev = NULL; >> + record_first.next = NULL; >> + record_first.type = record_end; >> + record_first.u.need_dasm = 0; >> + >> + init_record_ops (); >> + add_target (&record_ops); >> + >> + add_setshow_zinteger_cmd ("record", no_class, &record_debug, >> + _("Set debugging of record/replay feature."), >> + _("Show debugging of record/replay feature."), >> + _("When enabled, debugging output for " >> + "record/replay feature is displayed."), >> + NULL, show_record_debug, &setdebuglist, >> + &showdebuglist); >> + >> + add_com ("record", class_obscure, cmd_record_start, >> + _("Abbreviated form of \"target record\" command.")); >> + >> + add_com_alias ("rec", "record", class_obscure, 1); >> + >> + /* XXX: I try to use some simple commands such as "disconnect" and >> + "detach" to support this functions. But these commands all have >> + other affect to GDB such as call function "no_shared_libraries". >> + So I add special commands to GDB. */ >> + add_com ("delrecord", class_obscure, cmd_record_delete, >> + _("Delete the rest of execution log and start recording it anew.")); >> + add_com_alias ("dr", "delrecord", class_obscure, 1); >> + add_com ("stoprecord", class_obscure, cmd_record_stop, >> + _("Stop the record/replay target.")); >> + add_com_alias ("sr", "stoprecord", class_obscure, 1); >> + >> + /* Record instructions number limit command. */ >> + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, >> + &record_stop_at_limit, >> + _("Set whether record/replay stop when " >> + "record/replay buffer becomes full."), >> + _("Show whether record/replay stop when " >> + "record/replay buffer becomes full."), >> + _("Enable is default value.\n" >> + "When enabled, if the record/replay buffer " >> + "becomes full,\n" >> + "ask user what to do.\n" >> + "When disabled, if the record/replay buffer " >> + "becomes full,\n" >> + "delete it and start new recording."), >> + NULL, NULL, &setlist, &showlist); >> + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, >> + &record_insn_max_num, >> + _("Set record/replay buffer limit."), >> + _("Show record/replay buffer limit."), >> + _("Set the maximum number of instructions to be " >> + "stored in the\n" >> + "record/replay buffer. " >> + "Zero means unlimited (default 200000)."), >> + set_record_insn_max_num, >> + NULL, &setlist, &showlist); >> + add_info ("record-insn-number", show_record_insn_number, >> + _("Show the current number of instructions in the " >> + "record/replay buffer.")); >> +} >> Index: src/gdb/record.h >> =================================================================== >> --- /dev/null 1970-01-01 00:00:00.000000000 +0000 >> +++ src/gdb/record.h 2009-02-28 20:23:20.000000000 +0000 >> @@ -0,0 +1,87 @@ >> +/* Process record and replay target for GDB, the GNU debugger. >> + >> + Copyright (C) 2008 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/>. */ >> + >> +#ifndef _RECORD_H_ >> +#define _RECORD_H_ >> + >> +#define TARGET_IS_PROCESS_RECORD \ >> + (current_target.beneath == &record_ops) > > Sorry, but I repeat the request I've made several times already. This is > not the right way to do this. You need to add a new target_ops method or > property that the core of GDB checks on. It is not correct that make > the core of GDB reference record_ops directly. To come up with > the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, > consider what is the property of the current target that GDB needs to > know about the current target. Is it something like: > > target_is_recording () ? > target_is_replaying () ? > target_is_read_only () ? > > etc. > I forget a process record has special strata "record_stratum". What about delete "TARGET_IS_PROCESS_RECORD" and add #define target_is_record (target) (target->to_stratum == record_stratum) to target.h? >> +#define RECORD_IS_REPLAY \ >> + (record_list->next || execution_direction == EXEC_REVERSE) > > AFAICS, this macro is not used outside of record.c. It should move > there, along with anything that isn't used outside of record.c. OK. I will put it to record.c. > >> + >> +typedef struct record_reg_s >> +{ >> + int num; >> + gdb_byte *val; >> +} record_reg_t; >> + >> +typedef struct record_mem_s >> +{ >> + CORE_ADDR addr; >> + int len; >> + gdb_byte *val; >> +} record_mem_t; >> + >> +enum record_type >> +{ >> + record_end = 0, >> + record_reg, >> + record_mem >> +}; >> + >> +/* This is the core struct of record function. >> + >> + An entity of record_t is a record of the value change of a register >> + ("record_reg") or a part of memory ("record_mem"). And each >> + instruction must has a record_t ("record_end") that points out this >> + is the last record_t of this instruction. >> + >> + Each record_t is linked to "record_list" by "prev" and "next". >> + */ >> +typedef struct record_s >> +{ >> + struct record_s *prev; >> + struct record_s *next; >> + enum record_type type; >> + union >> + { >> + /* reg */ >> + record_reg_t reg; >> + /* mem */ >> + record_mem_t mem; >> + /* end */ >> + int need_dasm; >> + } u; >> +} record_t; >> + >> +extern int record_debug; >> +extern record_t *record_list; >> +extern record_t *record_arch_list_head; >> +extern record_t *record_arch_list_tail; >> +extern struct regcache *record_regcache; > > Most of these things don't appear to be used anywhere else other > than in record.c. Can you remove these declarations from the > public header, and make them static in record.c? I will move them to record.c if just record.c use them. > >> + >> +extern struct target_ops record_ops; > > Once you get rid of TARGET_IS_PROCESS_RECORD this doesn't > need to be public anymore. I will. Thanks for you help. Hui ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-10 17:32 ` teawater @ 2009-03-10 19:35 ` Pedro Alves 2009-03-11 1:15 ` teawater ` (2 more replies) 0 siblings, 3 replies; 38+ messages in thread From: Pedro Alves @ 2009-03-10 19:35 UTC (permalink / raw) To: teawater; +Cc: gdb-patches, Michael Snyder On Tuesday 10 March 2009 17:02:46, teawater wrote: > >> +#define TARGET_IS_PROCESS_RECORD \ > >> + (current_target.beneath == &record_ops) > > > > Sorry, but I repeat the request I've made several times already. This is > > not the right way to do this. You need to add a new target_ops method or > > property that the core of GDB checks on. It is not correct that make > > the core of GDB reference record_ops directly. To come up with > > the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, > > consider what is the property of the current target that GDB needs to > > know about the current target. Is it something like: > > > > target_is_recording () ? > > target_is_replaying () ? > > target_is_read_only () ? > > > > etc. > > > > I forget a process record has special strata "record_stratum". > > What about delete "TARGET_IS_PROCESS_RECORD" and add > #define target_is_record (target) (target->to_stratum == record_stratum) > to target.h? No, that's not a new callback... If we in some hypothetical future end up layering yet another target on top of record, then that check will fail. What I'm saying is, TARGET_IS_PROCESS_RECORD is just as wrong as TARGET_IS_LINUX_NAT_C, or TARGET_IS_WINDOWS_NAT_C. E.g., in places in the common code that we want to check if the current target has execution, we call target_has_execution, doesn't matter which target it is, as long as it has execution. If we want to check that the target is in asynchronous more, we check for target_is_async_p, again, doesn't matter which target it is. In your case, imagine that you implemented all of record.c in gdbserver instead of on GDB. Say, imagine that there's no record.c in GDB at all. Then, when you connected to gdbserver, and told it to start recording, the topmost pushed target on the GDB side would still be the remote target (process stratum). GDB wouldn't know how gdbserver was implementing the recording feature, only that the remote side supports the recording feature. Now, see, here's a property we'd possibly want to expose through a target method --- e.g., target_can_record_p(). If you look at the places in the core of GDB where you are currently checking for TARGET_IS_PROCESS_RECORD, and you wanted those checks to also return true in the case of precord being implemented on the remote server, clearly, you'd need to check for some target property other than the target name, or its stratum. Why is it that you need to call TARGET_IS_PROCESS_RECORD in the first place? That is the key here. Again, is it because the target is recording, and you can't do some things in that case? Is it because the target is replaying? Or perhaps it's a more fundamental state --- is the target in a read only state? When you find out which *state(s)* you're interesting in checking, we can easily add target method(s) for it/them, and make the record target implement them (say, returning true), and making the default return false. Or, we may even come to the conclusion that is not a target method we want --- we may end up with a global variable, similar to `execution_direction'. -- Pedro Alves ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-10 19:35 ` Pedro Alves @ 2009-03-11 1:15 ` teawater 2009-03-13 0:27 ` teawater 2009-03-16 11:21 ` teawater 2009-03-18 8:50 ` teawater 2009-03-18 13:12 ` teawater 2 siblings, 2 replies; 38+ messages in thread From: teawater @ 2009-03-11 1:15 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Michael Snyder Hi Pedro, On Wed, Mar 11, 2009 at 02:00, Pedro Alves <pedro@codesourcery.com> wrote: > On Tuesday 10 March 2009 17:02:46, teawater wrote: >> >> +#define TARGET_IS_PROCESS_RECORD \ >> >> + (current_target.beneath == &record_ops) >> > >> > Sorry, but I repeat the request I've made several times already. This is >> > not the right way to do this. You need to add a new target_ops method or >> > property that the core of GDB checks on. It is not correct that make >> > the core of GDB reference record_ops directly. To come up with >> > the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, >> > consider what is the property of the current target that GDB needs to >> > know about the current target. Is it something like: >> > >> > target_is_recording () ? >> > target_is_replaying () ? >> > target_is_read_only () ? >> > >> > etc. >> > >> >> I forget a process record has special strata "record_stratum". >> >> What about delete "TARGET_IS_PROCESS_RECORD" and add >> #define target_is_record (target) (target->to_stratum == record_stratum) >> to target.h? > > No, that's not a new callback... > > If we in some hypothetical future end up layering yet another target > on top of record, then that check will fail. > > What I'm saying is, TARGET_IS_PROCESS_RECORD is just as wrong > as TARGET_IS_LINUX_NAT_C, or TARGET_IS_WINDOWS_NAT_C. > > E.g., in places in the common code that we want to check if the > current target has execution, we call target_has_execution, doesn't > matter which target it is, as long as it has execution. If we want to > check that the target is in asynchronous more, we check for > target_is_async_p, again, doesn't matter which target it is. > > In your case, imagine that you implemented all of record.c in > gdbserver instead of on GDB. Say, imagine that there's no record.c in > GDB at all. Then, when you connected to gdbserver, and told it to > start recording, the topmost pushed target on the GDB side would still be > the remote target (process stratum). GDB wouldn't know how gdbserver > was implementing the recording feature, only that the remote side supports > the recording feature. Now, see, here's a property we'd possibly > want to expose through a target method --- e.g., target_can_record_p(). > > If you look at the places in the core of GDB where you are > currently checking for TARGET_IS_PROCESS_RECORD, and you wanted those > checks to also return true in the case of precord being implemented > on the remote server, clearly, you'd need to check for some > target property other than the target name, or its stratum. > > Why is it that you need to call TARGET_IS_PROCESS_RECORD in the > first place? That is the key here. Again, is it because the > target is recording, and you can't do some things in that case? > Is it because the target is replaying? Or perhaps it's a more > fundamental state --- is the target in a read only state? When you > find out which *state(s)* you're interesting in checking, we can > easily add target method(s) for it/them, and make the record > target implement them (say, returning true), and making the > default return false. Or, we may even come to the conclusion > that is not a target method we want --- we may end up with > a global variable, similar to `execution_direction'. > TARGET_IS_PROCESS_RECORD is not very well. I agree with it. Let's talk about "#define target_is_record (target) (target->to_stratum == record_stratum)". If add a new callback, it's mean that every target can be a process record or not. But process record is not a function, it's a target. For example: static int use_displaced_stepping (struct gdbarch *gdbarch) { return (((can_use_displaced_stepping == can_use_displaced_stepping_auto && non_stop) || can_use_displaced_stepping == can_use_displaced_stepping_on) && gdbarch_displaced_step_copy_insn_p (gdbarch) && !TARGET_IS_PROCESS_RECORD); } This place use "TARGET_IS_PROCESS_RECORD" because process record target can't work with displaced stepping. if (TARGET_IS_PROCESS_RECORD) old_cleanups = record_gdb_operation_disable_set (); if (singlestep_breakpoints_inserted_p || !ptid_equal (ecs->ptid, inferior_ptid) || !currently_stepping (ecs->event_thread) || ecs->event_thread->prev_pc == breakpoint_pc) regcache_write_pc (regcache, breakpoint_pc); if (TARGET_IS_PROCESS_RECORD) do_cleanups (old_cleanups); This place use "TARGET_IS_PROCESS_RECORD" because process record don't want record this pc change. Off course, in global code call a macro in record.h is ugly. I agree. But "#define target_is_process_record (target) (target->to_stratum == record_stratum)". is better. record_stratum is in enum strata { dummy_stratum, /* The lowest of the low */ file_stratum, /* Executable files, etc */ core_stratum, /* Core dump files */ process_stratum, /* Executing processes */ thread_stratum, /* Executing threads */ record_stratum /* Support record debugging */ }; in target.h. It's used to make process record on the top of all other target. Just process record use it. I think it friendly to both global code and process record target. Thanks, Hui ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-11 1:15 ` teawater @ 2009-03-13 0:27 ` teawater 2009-03-16 11:21 ` teawater 1 sibling, 0 replies; 38+ messages in thread From: teawater @ 2009-03-13 0:27 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Michael Snyder Hi Pedro, Sorry to disturb you. Could you please help me review it? Thanks, Hui On Wed, Mar 11, 2009 at 07:37, teawater <teawater@gmail.com> wrote: > Hi Pedro, > > On Wed, Mar 11, 2009 at 02:00, Pedro Alves <pedro@codesourcery.com> wrote: >> On Tuesday 10 March 2009 17:02:46, teawater wrote: >>> >> +#define TARGET_IS_PROCESS_RECORD \ >>> >> + (current_target.beneath == &record_ops) >>> > >>> > Sorry, but I repeat the request I've made several times already. This is >>> > not the right way to do this. You need to add a new target_ops method or >>> > property that the core of GDB checks on. It is not correct that make >>> > the core of GDB reference record_ops directly. To come up with >>> > the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, >>> > consider what is the property of the current target that GDB needs to >>> > know about the current target. Is it something like: >>> > >>> > target_is_recording () ? >>> > target_is_replaying () ? >>> > target_is_read_only () ? >>> > >>> > etc. >>> > >>> >>> I forget a process record has special strata "record_stratum". >>> >>> What about delete "TARGET_IS_PROCESS_RECORD" and add >>> #define target_is_record (target) (target->to_stratum == record_stratum) >>> to target.h? >> >> No, that's not a new callback... >> >> If we in some hypothetical future end up layering yet another target >> on top of record, then that check will fail. >> >> What I'm saying is, TARGET_IS_PROCESS_RECORD is just as wrong >> as TARGET_IS_LINUX_NAT_C, or TARGET_IS_WINDOWS_NAT_C. >> >> E.g., in places in the common code that we want to check if the >> current target has execution, we call target_has_execution, doesn't >> matter which target it is, as long as it has execution. If we want to >> check that the target is in asynchronous more, we check for >> target_is_async_p, again, doesn't matter which target it is. >> >> In your case, imagine that you implemented all of record.c in >> gdbserver instead of on GDB. Say, imagine that there's no record.c in >> GDB at all. Then, when you connected to gdbserver, and told it to >> start recording, the topmost pushed target on the GDB side would still be >> the remote target (process stratum). GDB wouldn't know how gdbserver >> was implementing the recording feature, only that the remote side supports >> the recording feature. Now, see, here's a property we'd possibly >> want to expose through a target method --- e.g., target_can_record_p(). >> >> If you look at the places in the core of GDB where you are >> currently checking for TARGET_IS_PROCESS_RECORD, and you wanted those >> checks to also return true in the case of precord being implemented >> on the remote server, clearly, you'd need to check for some >> target property other than the target name, or its stratum. >> >> Why is it that you need to call TARGET_IS_PROCESS_RECORD in the >> first place? That is the key here. Again, is it because the >> target is recording, and you can't do some things in that case? >> Is it because the target is replaying? Or perhaps it's a more >> fundamental state --- is the target in a read only state? When you >> find out which *state(s)* you're interesting in checking, we can >> easily add target method(s) for it/them, and make the record >> target implement them (say, returning true), and making the >> default return false. Or, we may even come to the conclusion >> that is not a target method we want --- we may end up with >> a global variable, similar to `execution_direction'. >> > > TARGET_IS_PROCESS_RECORD is not very well. I agree with it. > Let's talk about "#define target_is_record (target) > (target->to_stratum == record_stratum)". > > If add a new callback, it's mean that every target can be a process > record or not. > But process record is not a function, it's a target. > For example: > static int > use_displaced_stepping (struct gdbarch *gdbarch) > { > return (((can_use_displaced_stepping == can_use_displaced_stepping_auto > && non_stop) > || can_use_displaced_stepping == can_use_displaced_stepping_on) > && gdbarch_displaced_step_copy_insn_p (gdbarch) > && !TARGET_IS_PROCESS_RECORD); > } > > This place use "TARGET_IS_PROCESS_RECORD" because process record > target can't work with displaced stepping. > > if (TARGET_IS_PROCESS_RECORD) > old_cleanups = record_gdb_operation_disable_set (); > > if (singlestep_breakpoints_inserted_p > || !ptid_equal (ecs->ptid, inferior_ptid) > || !currently_stepping (ecs->event_thread) > || ecs->event_thread->prev_pc == breakpoint_pc) > regcache_write_pc (regcache, breakpoint_pc); > > if (TARGET_IS_PROCESS_RECORD) > do_cleanups (old_cleanups); > > This place use "TARGET_IS_PROCESS_RECORD" because process record don't > want record this pc change. > > Off course, in global code call a macro in record.h is ugly. I agree. > > But "#define target_is_process_record (target) (target->to_stratum == > record_stratum)". is better. > record_stratum is in enum strata > { > dummy_stratum, /* The lowest of the low */ > file_stratum, /* Executable files, etc */ > core_stratum, /* Core dump files */ > process_stratum, /* Executing processes */ > thread_stratum, /* Executing threads */ > record_stratum /* Support record debugging */ > }; > in target.h. > It's used to make process record on the top of all other target. Just > process record use it. > I think it friendly to both global code and process record target. > > > Thanks, > Hui > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-11 1:15 ` teawater 2009-03-13 0:27 ` teawater @ 2009-03-16 11:21 ` teawater 1 sibling, 0 replies; 38+ messages in thread From: teawater @ 2009-03-16 11:21 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Michael Snyder Hi Pedro, Sorry to disturb you. Could you please help me review it? Thanks, Hui On Wed, Mar 11, 2009 at 07:37, teawater <teawater@gmail.com> wrote: > Hi Pedro, > > On Wed, Mar 11, 2009 at 02:00, Pedro Alves <pedro@codesourcery.com> wrote: >> On Tuesday 10 March 2009 17:02:46, teawater wrote: >>> >> +#define TARGET_IS_PROCESS_RECORD \ >>> >> + (current_target.beneath == &record_ops) >>> > >>> > Sorry, but I repeat the request I've made several times already. This is >>> > not the right way to do this. You need to add a new target_ops method or >>> > property that the core of GDB checks on. It is not correct that make >>> > the core of GDB reference record_ops directly. To come up with >>> > the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, >>> > consider what is the property of the current target that GDB needs to >>> > know about the current target. Is it something like: >>> > >>> > target_is_recording () ? >>> > target_is_replaying () ? >>> > target_is_read_only () ? >>> > >>> > etc. >>> > >>> >>> I forget a process record has special strata "record_stratum". >>> >>> What about delete "TARGET_IS_PROCESS_RECORD" and add >>> #define target_is_record (target) (target->to_stratum == record_stratum) >>> to target.h? >> >> No, that's not a new callback... >> >> If we in some hypothetical future end up layering yet another target >> on top of record, then that check will fail. >> >> What I'm saying is, TARGET_IS_PROCESS_RECORD is just as wrong >> as TARGET_IS_LINUX_NAT_C, or TARGET_IS_WINDOWS_NAT_C. >> >> E.g., in places in the common code that we want to check if the >> current target has execution, we call target_has_execution, doesn't >> matter which target it is, as long as it has execution. If we want to >> check that the target is in asynchronous more, we check for >> target_is_async_p, again, doesn't matter which target it is. >> >> In your case, imagine that you implemented all of record.c in >> gdbserver instead of on GDB. Say, imagine that there's no record.c in >> GDB at all. Then, when you connected to gdbserver, and told it to >> start recording, the topmost pushed target on the GDB side would still be >> the remote target (process stratum). GDB wouldn't know how gdbserver >> was implementing the recording feature, only that the remote side supports >> the recording feature. Now, see, here's a property we'd possibly >> want to expose through a target method --- e.g., target_can_record_p(). >> >> If you look at the places in the core of GDB where you are >> currently checking for TARGET_IS_PROCESS_RECORD, and you wanted those >> checks to also return true in the case of precord being implemented >> on the remote server, clearly, you'd need to check for some >> target property other than the target name, or its stratum. >> >> Why is it that you need to call TARGET_IS_PROCESS_RECORD in the >> first place? That is the key here. Again, is it because the >> target is recording, and you can't do some things in that case? >> Is it because the target is replaying? Or perhaps it's a more >> fundamental state --- is the target in a read only state? When you >> find out which *state(s)* you're interesting in checking, we can >> easily add target method(s) for it/them, and make the record >> target implement them (say, returning true), and making the >> default return false. Or, we may even come to the conclusion >> that is not a target method we want --- we may end up with >> a global variable, similar to `execution_direction'. >> > > TARGET_IS_PROCESS_RECORD is not very well. I agree with it. > Let's talk about "#define target_is_record (target) > (target->to_stratum == record_stratum)". > > If add a new callback, it's mean that every target can be a process > record or not. > But process record is not a function, it's a target. > For example: > static int > use_displaced_stepping (struct gdbarch *gdbarch) > { > return (((can_use_displaced_stepping == can_use_displaced_stepping_auto > && non_stop) > || can_use_displaced_stepping == can_use_displaced_stepping_on) > && gdbarch_displaced_step_copy_insn_p (gdbarch) > && !TARGET_IS_PROCESS_RECORD); > } > > This place use "TARGET_IS_PROCESS_RECORD" because process record > target can't work with displaced stepping. > > if (TARGET_IS_PROCESS_RECORD) > old_cleanups = record_gdb_operation_disable_set (); > > if (singlestep_breakpoints_inserted_p > || !ptid_equal (ecs->ptid, inferior_ptid) > || !currently_stepping (ecs->event_thread) > || ecs->event_thread->prev_pc == breakpoint_pc) > regcache_write_pc (regcache, breakpoint_pc); > > if (TARGET_IS_PROCESS_RECORD) > do_cleanups (old_cleanups); > > This place use "TARGET_IS_PROCESS_RECORD" because process record don't > want record this pc change. > > Off course, in global code call a macro in record.h is ugly. I agree. > > But "#define target_is_process_record (target) (target->to_stratum == > record_stratum)". is better. > record_stratum is in enum strata > { > dummy_stratum, /* The lowest of the low */ > file_stratum, /* Executable files, etc */ > core_stratum, /* Core dump files */ > process_stratum, /* Executing processes */ > thread_stratum, /* Executing threads */ > record_stratum /* Support record debugging */ > }; > in target.h. > It's used to make process record on the top of all other target. Just > process record use it. > I think it friendly to both global code and process record target. > > > Thanks, > Hui > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-10 19:35 ` Pedro Alves 2009-03-11 1:15 ` teawater @ 2009-03-18 8:50 ` teawater 2009-03-18 13:12 ` teawater 2 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-03-18 8:50 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Michael Snyder For the "record_debug" and "record_regcache", it's used in "i386-tdep.c". It must be extern. Thanks, Hui On Wed, Mar 11, 2009 at 02:00, Pedro Alves <pedro@codesourcery.com> wrote: > On Tuesday 10 March 2009 17:02:46, teawater wrote: >> >> +#define TARGET_IS_PROCESS_RECORD \ >> >> + (current_target.beneath == &record_ops) >> > >> > Sorry, but I repeat the request I've made several times already. This is >> > not the right way to do this. You need to add a new target_ops method or >> > property that the core of GDB checks on. It is not correct that make >> > the core of GDB reference record_ops directly. To come up with >> > the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, >> > consider what is the property of the current target that GDB needs to >> > know about the current target. Is it something like: >> > >> > target_is_recording () ? >> > target_is_replaying () ? >> > target_is_read_only () ? >> > >> > etc. >> > >> >> I forget a process record has special strata "record_stratum". >> >> What about delete "TARGET_IS_PROCESS_RECORD" and add >> #define target_is_record (target) (target->to_stratum == record_stratum) >> to target.h? > > No, that's not a new callback... > > If we in some hypothetical future end up layering yet another target > on top of record, then that check will fail. > > What I'm saying is, TARGET_IS_PROCESS_RECORD is just as wrong > as TARGET_IS_LINUX_NAT_C, or TARGET_IS_WINDOWS_NAT_C. > > E.g., in places in the common code that we want to check if the > current target has execution, we call target_has_execution, doesn't > matter which target it is, as long as it has execution. If we want to > check that the target is in asynchronous more, we check for > target_is_async_p, again, doesn't matter which target it is. > > In your case, imagine that you implemented all of record.c in > gdbserver instead of on GDB. Say, imagine that there's no record.c in > GDB at all. Then, when you connected to gdbserver, and told it to > start recording, the topmost pushed target on the GDB side would still be > the remote target (process stratum). GDB wouldn't know how gdbserver > was implementing the recording feature, only that the remote side supports > the recording feature. Now, see, here's a property we'd possibly > want to expose through a target method --- e.g., target_can_record_p(). > > If you look at the places in the core of GDB where you are > currently checking for TARGET_IS_PROCESS_RECORD, and you wanted those > checks to also return true in the case of precord being implemented > on the remote server, clearly, you'd need to check for some > target property other than the target name, or its stratum. > > Why is it that you need to call TARGET_IS_PROCESS_RECORD in the > first place? That is the key here. Again, is it because the > target is recording, and you can't do some things in that case? > Is it because the target is replaying? Or perhaps it's a more > fundamental state --- is the target in a read only state? When you > find out which *state(s)* you're interesting in checking, we can > easily add target method(s) for it/them, and make the record > target implement them (say, returning true), and making the > default return false. Or, we may even come to the conclusion > that is not a target method we want --- we may end up with > a global variable, similar to `execution_direction'. > > -- > Pedro Alves > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-10 19:35 ` Pedro Alves 2009-03-11 1:15 ` teawater 2009-03-18 8:50 ` teawater @ 2009-03-18 13:12 ` teawater 2 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-03-18 13:12 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Michael Snyder I had remove the "TARGET_IS_PROCESS_RECORD". Thanks, Hui On Wed, Mar 11, 2009 at 02:00, Pedro Alves <pedro@codesourcery.com> wrote: > On Tuesday 10 March 2009 17:02:46, teawater wrote: >> >> +#define TARGET_IS_PROCESS_RECORD \ >> >> + (current_target.beneath == &record_ops) >> > >> > Sorry, but I repeat the request I've made several times already. This is >> > not the right way to do this. You need to add a new target_ops method or >> > property that the core of GDB checks on. It is not correct that make >> > the core of GDB reference record_ops directly. To come up with >> > the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, >> > consider what is the property of the current target that GDB needs to >> > know about the current target. Is it something like: >> > >> > target_is_recording () ? >> > target_is_replaying () ? >> > target_is_read_only () ? >> > >> > etc. >> > >> >> I forget a process record has special strata "record_stratum". >> >> What about delete "TARGET_IS_PROCESS_RECORD" and add >> #define target_is_record (target) (target->to_stratum == record_stratum) >> to target.h? > > No, that's not a new callback... > > If we in some hypothetical future end up layering yet another target > on top of record, then that check will fail. > > What I'm saying is, TARGET_IS_PROCESS_RECORD is just as wrong > as TARGET_IS_LINUX_NAT_C, or TARGET_IS_WINDOWS_NAT_C. > > E.g., in places in the common code that we want to check if the > current target has execution, we call target_has_execution, doesn't > matter which target it is, as long as it has execution. If we want to > check that the target is in asynchronous more, we check for > target_is_async_p, again, doesn't matter which target it is. > > In your case, imagine that you implemented all of record.c in > gdbserver instead of on GDB. Say, imagine that there's no record.c in > GDB at all. Then, when you connected to gdbserver, and told it to > start recording, the topmost pushed target on the GDB side would still be > the remote target (process stratum). GDB wouldn't know how gdbserver > was implementing the recording feature, only that the remote side supports > the recording feature. Now, see, here's a property we'd possibly > want to expose through a target method --- e.g., target_can_record_p(). > > If you look at the places in the core of GDB where you are > currently checking for TARGET_IS_PROCESS_RECORD, and you wanted those > checks to also return true in the case of precord being implemented > on the remote server, clearly, you'd need to check for some > target property other than the target name, or its stratum. > > Why is it that you need to call TARGET_IS_PROCESS_RECORD in the > first place? That is the key here. Again, is it because the > target is recording, and you can't do some things in that case? > Is it because the target is replaying? Or perhaps it's a more > fundamental state --- is the target in a read only state? When you > find out which *state(s)* you're interesting in checking, we can > easily add target method(s) for it/them, and make the record > target implement them (say, returning true), and making the > default return false. Or, we may even come to the conclusion > that is not a target method we want --- we may end up with > a global variable, similar to `execution_direction'. > > -- > Pedro Alves > ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-09 20:35 ` Pedro Alves 2009-03-10 17:32 ` teawater @ 2009-03-18 13:05 ` teawater 2009-03-18 13:14 ` teawater 2 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-03-18 13:05 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Marc Khouzam > > >> + >> +static void >> +record_disconnect (struct target_ops *target, char *args, int from_tty) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); >> + >> + unpush_target (&record_ops); >> + target_disconnect (args, from_tty); >> +} >> + >> +static void >> +record_detach (struct target_ops *ops, char *args, int from_tty) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); >> + >> + unpush_target (&record_ops); >> + target_detach (args, from_tty); >> +} > > This trick you're using happens to work, but, could you try > this instead? Here and elsewhere similarly. > > struct target_ops *beneath = find_target_beaneath (ops); > unpush_target (ops); > beneath->to_detach (args, from_tty); > If beneath->to_detach is NULL, it will make gdb crash. Thanks, Hui ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-09 20:35 ` Pedro Alves 2009-03-10 17:32 ` teawater 2009-03-18 13:05 ` teawater @ 2009-03-18 13:14 ` teawater 2009-03-18 13:54 ` teawater 2 siblings, 1 reply; 38+ messages in thread From: teawater @ 2009-03-18 13:14 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Marc Khouzam, Michael Snyder [-- Attachment #1: Type: text/plain, Size: 56449 bytes --] Hi Pedro, I make a new patch according to your mail. Please help me review it. Thanks, Hui On Tue, Mar 10, 2009 at 04:34, Pedro Alves <pedro@codesourcery.com> wrote: > On Monday 23 February 2009 09:20:13, teawater wrote: >> This is the new version patches that follow cvs-head. > > Thanks. > >> >> gdb/Makefile.in | 4 >> gdb/record.c | 1283 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ >> gdb/record.h | 87 +++ >> 3 files changed, 1372 insertions(+), 2 deletions(-) >> >> Index: src/gdb/Makefile.in >> =================================================================== >> --- src.orig/gdb/Makefile.in 2009-02-21 15:53:27.000000000 +0000 >> +++ src/gdb/Makefile.in 2009-02-28 20:23:20.000000000 +0000 >> @@ -662,7 +662,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr >> valarith.c valops.c valprint.c value.c varobj.c vec.c \ >> wrapper.c \ >> xml-tdesc.c xml-support.c \ >> - inferior.c >> + inferior.c record.c >> >> LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c >> >> @@ -813,7 +813,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ >> solib.o solib-null.o \ >> prologue-value.o memory-map.o xml-support.o \ >> target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ >> - inferior.o osdata.o >> + inferior.o osdata.o record.o >> >> TSOBS = inflow.o >> >> Index: src/gdb/record.c >> =================================================================== >> --- /dev/null 1970-01-01 00:00:00.000000000 +0000 >> +++ src/gdb/record.c 2009-02-28 20:23:20.000000000 +0000 >> @@ -0,0 +1,1283 @@ >> +/* Process record and replay target for GDB, the GNU debugger. >> + >> + Copyright (C) 2008 Free Software Foundation, Inc. > > Oops, you'll have to update this. > >> + >> + This file is part of GDB. >> + >> + This program is free software; you can redistribute it and/or modify >> + it under the terms of the GNU General Public License as published by >> + the Free Software Foundation; either version 3 of the License, or >> + (at your option) any later version. >> + >> + This program is distributed in the hope that it will be useful, >> + but WITHOUT ANY WARRANTY; without even the implied warranty of >> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + GNU General Public License for more details. >> + >> + You should have received a copy of the GNU General Public License >> + along with this program. If not, see <http://www.gnu.org/licenses/>. */ >> + >> +#include "defs.h" >> +#include "target.h" >> +#include "gdbcmd.h" >> +#include "regcache.h" >> +#include "inferior.h" >> +#include "gdbthread.h" >> +#include "event-top.h" >> +#include "annotate.h" >> +#include "observer.h" >> +#include "record.h" > > Why did you need annotate.h? Can you check which headers are > really needed here? > >> + >> +#include <signal.h> >> + >> +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 >> + >> +int record_debug = 0; >> + >> +record_t record_first; >> +record_t *record_list = &record_first; >> +record_t *record_arch_list_head = NULL; >> +record_t *record_arch_list_tail = NULL; >> +struct regcache *record_regcache = NULL; >> + >> +/* 1 ask user. 0 auto delete the last record_t. */ > ^ double-space please. > >> +static int record_stop_at_limit = 1; >> +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; >> +static int record_insn_num = 0; >> + >> +struct target_ops record_ops; >> +static int record_resume_step = 0; >> +static enum target_signal record_resume_siggnal; >> +static int record_get_sig = 0; >> +static sigset_t record_maskall; >> +static int record_gdb_operation_disable = 0; >> +int record_will_store_registers = 0; > > You've got a bunch of magic variables here. Could you > add some comments explaining what they're for? > >> + >> +extern struct bp_location *bp_location_chain; > > This is not right. You're accessing a breakpoints.c > internal implementation detail. We need to come up with interfaces > to replace this hack. > >> + >> +/* The beneath function pointers. */ >> +static struct target_ops *record_beneath_to_resume_ops = NULL; >> +static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, >> + enum target_signal) = NULL; >> +static struct target_ops *record_beneath_to_wait_ops = NULL; >> +static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, >> + struct target_waitstatus *) = NULL; >> +static struct target_ops *record_beneath_to_store_registers_ops = NULL; >> +static void (*record_beneath_to_store_registers) (struct target_ops *, >> + struct regcache *, >> + int regno) = NULL; >> +static struct target_ops *record_beneath_to_xfer_partial_ops = NULL; >> +static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, >> + enum target_object object, >> + const char *annex, >> + gdb_byte * readbuf, >> + const gdb_byte * writebuf, >> + ULONGEST offset, >> + LONGEST len) = NULL; >> +static int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *) = >> + NULL; >> +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *) = >> + NULL; > > I can't say I'm happy with this mechanism yet, but it is much > better than the previous version of making target.c aware of record.c's callbacks. > > >> + >> +static void >> +record_list_release (record_t * rec) > > ^ no space after *, please. > >> +{ >> + record_t *tmp; >> + >> + if (!rec) >> + return; >> + >> + while (rec->next) >> + { >> + rec = rec->next; >> + } >> + >> + while (rec->prev) >> + { >> + tmp = rec; >> + rec = rec->prev; >> + if (tmp->type == record_reg) >> + xfree (tmp->u.reg.val); >> + else if (tmp->type == record_mem) >> + xfree (tmp->u.mem.val); >> + xfree (tmp); >> + } >> + >> + if (rec != &record_first) >> + xfree (rec); >> +} >> + >> +static void >> +record_list_release_next (void) >> +{ >> + record_t *rec = record_list; >> + record_t *tmp = rec->next; >> + rec->next = NULL; >> + while (tmp) >> + { >> + rec = tmp->next; >> + if (tmp->type == record_reg) >> + record_insn_num--; >> + else if (tmp->type == record_reg) >> + xfree (tmp->u.reg.val); >> + else if (tmp->type == record_mem) >> + xfree (tmp->u.mem.val); >> + xfree (tmp); >> + tmp = rec; >> + } >> +} >> + >> +static void >> +record_list_release_first (void) >> +{ >> + record_t *tmp = NULL; >> + enum record_type type; >> + >> + if (!record_first.next) >> + return; >> + >> + while (1) >> + { >> + type = record_first.next->type; >> + >> + if (type == record_reg) >> + xfree (record_first.next->u.reg.val); >> + else if (type == record_mem) >> + xfree (record_first.next->u.mem.val); >> + tmp = record_first.next; >> + record_first.next = tmp->next; >> + xfree (tmp); >> + >> + if (!record_first.next) >> + { >> + gdb_assert (record_insn_num == 1); >> + break; >> + } >> + >> + record_first.next->prev = &record_first; >> + >> + if (type == record_end) >> + break; >> + } >> + >> + record_insn_num--; >> +} >> + >> +/* Add a record_t to record_arch_list. */ >> +static void >> +record_arch_list_add (record_t * rec) >> +{ >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_arch_list_add %s.\n", >> + host_address_to_string (rec)); >> + >> + if (record_arch_list_tail) >> + { >> + record_arch_list_tail->next = rec; >> + rec->prev = record_arch_list_tail; >> + record_arch_list_tail = rec; >> + } >> + else >> + { >> + record_arch_list_head = rec; >> + record_arch_list_tail = rec; >> + } >> +} >> + >> +/* Record the value of a register ("num") to record_arch_list. */ > > When we want to mention the value of a parameter, we mentions it in > uppercase, like so: > > /* Record the value of register NUM to record_arch_list. */ > > Also, there should be an empty line between the comment and > the function itself. Here and elsewhere. > >> +int >> +record_arch_list_add_reg (int num) >> +{ >> + record_t *rec; >> + >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: add register num = %d to " >> + "record list.\n", >> + num); >> + >> + rec = (record_t *) xmalloc (sizeof (record_t)); >> + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_reg; >> + rec->u.reg.num = num; >> + >> + regcache_raw_read (record_regcache, num, rec->u.reg.val); >> + >> + record_arch_list_add (rec); >> + >> + return 0; >> +} >> + >> +/* Record the value of a region of memory whose address is "addr" and >> + length is "len" to record_arch_list. */ > > /* Record the value of a region of memory whose address is ADDR and > length is LEN to record_arch_list. */ > >> + >> +int >> +record_arch_list_add_mem (CORE_ADDR addr, int len) >> +{ >> + record_t *rec; >> + >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: add mem addr = 0x%s len = %d to " >> + "record list.\n", >> + paddr_nz (addr), len); >> + > >> + if (!addr) >> + return 0; > > Why is this check for addr == 0 necessary here? > >> + >> + rec = (record_t *) xmalloc (sizeof (record_t)); >> + rec->u.mem.val = (gdb_byte *) xmalloc (len); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_mem; >> + rec->u.mem.addr = addr; >> + rec->u.mem.len = len; >> + >> + if (target_read_memory (addr, rec->u.mem.val, len)) >> + { >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: error reading memory at " >> + "addr = 0x%s len = %d.\n", >> + paddr_nz (addr), len); >> + xfree (rec->u.mem.val); >> + xfree (rec); >> + return -1; >> + } >> + >> + record_arch_list_add (rec); >> + >> + return 0; >> +} >> + >> +/* Add a record_end type record_t to record_arch_list. */ >> +int >> +record_arch_list_add_end (int need_dasm) >> +{ >> + record_t *rec; >> + >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: add end need_dasm = %d to " >> + "arch list.\n", >> + need_dasm); >> + >> + rec = (record_t *) xmalloc (sizeof (record_t)); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_end; >> + >> + rec->u.need_dasm = need_dasm; >> + >> + record_arch_list_add (rec); >> + >> + return 0; >> +} >> + >> +static void >> +record_check_insn_num (int set_terminal) >> +{ >> + if (record_insn_max_num) >> + { >> + gdb_assert (record_insn_num <= record_insn_max_num); >> + if (record_insn_num == record_insn_max_num) >> + { >> + /* Ask user what to do. */ >> + if (record_stop_at_limit) >> + { >> + int q; >> + if (set_terminal) >> + target_terminal_ours (); >> + q = yquery (_("Do you want to auto delete previous execution " >> + "log entries when record/replay buffer becomes " >> + "full (record-stop-at-limit)?")); >> + if (set_terminal) >> + target_terminal_inferior (); >> + if (q) >> + record_stop_at_limit = 0; >> + else >> + error (_("Process record: inferior program stopped.")); >> + } >> + } >> + } >> +} >> + >> +static void >> +record_normal_stop (void) >> +{ >> + finish_thread_state (minus_one_ptid); >> + >> + if (!breakpoints_always_inserted_mode () && target_has_execution) >> + remove_breakpoints (); >> + >> + target_terminal_ours (); >> + >> + if (target_has_stack && !stop_stack_dummy) >> + set_current_sal_from_frame (get_current_frame (), 1); >> + >> + select_frame (get_current_frame ()); >> + >> + annotate_stopped (); >> + if (!suppress_stop_observer) >> + { >> + if (!ptid_equal (inferior_ptid, null_ptid)) >> + observer_notify_normal_stop (inferior_thread ()->stop_bpstat, 0); >> + else >> + observer_notify_normal_stop (NULL, 0); >> + } >> +} > > Nope sorry, this is worse than before. When I asked to not call > normal_stop, I didn't mean that you should inline chunks of > it here. This is papering over a different problem. Why > is it that record.c needs to do this, while other targets do not? > If we need to do something like this, it should be needed by all > targets that can throw from target_wait (which, is documented > as something you shouldn't do anyway). Actually, do you still need > any of this in current head? > >> + >> +/* 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_list_release (record_arch_list_tail); >> + set_executing (inferior_ptid, 0); >> + record_normal_stop (); >> +} > > Same as above. This isn't a record.c problem. > >> + >> +void >> +record_message (struct gdbarch *gdbarch) >> +{ >> + int ret; >> + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); >> + >> + record_arch_list_head = NULL; >> + record_arch_list_tail = NULL; >> + >> + /* Check record_insn_num. */ >> + record_check_insn_num (1); >> + >> + record_regcache = get_current_regcache (); >> + >> + ret = gdbarch_process_record (gdbarch, >> + regcache_read_pc (record_regcache)); >> + if (ret > 0) >> + error (_("Process record: inferior program stopped.")); >> + if (ret < 0) >> + error (_("Process record: failed to record execution log.")); >> + >> + discard_cleanups (old_cleanups); >> + >> + record_list->next = record_arch_list_head; >> + record_arch_list_head->prev = record_list; >> + record_list = record_arch_list_tail; >> + >> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >> + record_list_release_first (); >> + else >> + record_insn_num++; >> +} >> + > > >> +/* Things to clean up if we error or QUIT out of function that set >> + record_gdb_operation_disable (ie. command that caused target_wait to >> + be called). */ >> +static void >> +record_gdb_operation_disable_cleanups (void *ignore) >> +{ >> + record_gdb_operation_disable = 0; >> +} >> + >> +struct cleanup * >> +record_gdb_operation_disable_set (void) >> +{ >> + struct cleanup *old_cleanups = >> + make_cleanup (record_gdb_operation_disable_cleanups, 0); >> + record_gdb_operation_disable = 1; >> + >> + return old_cleanups; >> +} > > This is make_cleanup_restore_integer. > >> + >> +static void >> +record_open (char *name, int from_tty) >> +{ >> + struct target_ops *t; >> + >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); >> + >> + /* check exec */ > > and core. > >> + if (!target_has_execution) >> + error (_("Process record: the program is not being run.")); >> + if (non_stop) >> + error (_("Process record target can't debug inferior in non-stop mode " >> + "(non-stop).")); >> + if (target_async_permitted) >> + error (_("Process record target can't debug inferior in asynchronous " >> + "mode (target-async).")); >> + >> + if (!gdbarch_process_record_p (current_gdbarch)) >> + error (_("Process record: the current architecture doesn't support " >> + "record function.")); >> + >> + /* Check if record target is already running. */ >> + if (TARGET_IS_PROCESS_RECORD) >> + { >> + if (!nquery >> + (_("Process record target already running, do you want to delete " >> + "the old record log?"))) >> + return; >> + } >> + >> + /* Set the beneath function pointers. */ >> + for (t = current_target.beneath; t != NULL; t = t->beneath) >> + { >> + if (!record_beneath_to_resume) >> + { >> + record_beneath_to_resume = t->to_resume; >> + record_beneath_to_resume_ops = t; >> + } >> + if (!record_beneath_to_wait) >> + { >> + record_beneath_to_wait = t->to_wait; >> + record_beneath_to_wait_ops = t; >> + } >> + if (!record_beneath_to_store_registers) >> + { >> + record_beneath_to_store_registers = t->to_store_registers; >> + record_beneath_to_store_registers_ops = t; >> + } >> + if (!record_beneath_to_xfer_partial) >> + { >> + record_beneath_to_xfer_partial = t->to_xfer_partial; >> + record_beneath_to_xfer_partial_ops = t; >> + } >> + if (!record_beneath_to_insert_breakpoint) >> + record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; >> + if (!record_beneath_to_remove_breakpoint) >> + record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; >> + } >> + if (!record_beneath_to_resume) >> + error (_("Process record can't get to_resume.")); >> + if (!record_beneath_to_wait) >> + error (_("Process record can't get to_wait.")); >> + if (!record_beneath_to_store_registers) >> + error (_("Process record can't get to_store_registers.")); >> + if (!record_beneath_to_xfer_partial) >> + error (_("Process record can't get to_xfer_partial.")); >> + if (!record_beneath_to_insert_breakpoint) >> + error (_("Process record can't get to_insert_breakpoint.")); >> + if (!record_beneath_to_remove_breakpoint) >> + error (_("Process record can't get to_remove_breakpoint.")); > > As I said above, this is better than making target.c aware of > these pointers, but, it still isn't ideal. For one thing, if > some other layer/target is pushed after the record target is opened, > these will be wrong. I would prefer that this beneath lookup > would be done at each callback implementation (record_resume, etc.), > but, I'm happy enough with this for now. It is now contained, so we can > clean this up afterwards... > >> + >> + push_target (&record_ops); >> + >> + /* Reset */ >> + record_insn_num = 0; >> + record_list = &record_first; >> + record_list->next = NULL; >> +} >> + >> +static void >> +record_close (int quitting) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); >> + >> + record_list_release (record_list); >> +} > > Shouldn't this clear the record_beneath_* pointers as well? > >> + >> +static void >> +record_resume (struct target_ops *ops, ptid_t ptid, int step, >> + enum target_signal siggnal) >> +{ >> + record_resume_step = step; >> + record_resume_siggnal = siggnal; >> + >> + if (!RECORD_IS_REPLAY) >> + { >> + record_message (current_gdbarch); >> + record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, >> + siggnal); >> + } >> +} >> + >> +static void >> +record_sig_handler (int signo) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); >> + >> + record_resume_step = 1; >> + record_get_sig = 1; >> +} > > This handler is magical. Why is it setting resume_step, for instance? > It would definitelly benefic from some comments. In fact, most of the > file is undercommented. > >> + >> +static void >> +record_wait_cleanups (void *ignore) >> +{ >> + if (execution_direction == EXEC_REVERSE) >> + { >> + if (record_list->next) >> + record_list = record_list->next; >> + } >> + else >> + record_list = record_list->prev; >> + >> + set_executing (inferior_ptid, 0); >> + record_normal_stop (); >> +} > > See comments about record_normal_stop above. > >> + >> +/* record_wait > > Please remove the function name from the comment. It's redundant. > >> + In replay mode, this function examines the recorded log and >> + determines where to stop. */ >> + >> +static ptid_t >> +record_wait (struct target_ops *ops, >> + ptid_t ptid, struct target_waitstatus *status) >> +{ >> + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); >> + >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_wait " >> + "record_resume_step = %d\n", >> + record_resume_step); >> + >> + if (!RECORD_IS_REPLAY) >> + { >> + if (record_resume_step) >> + { >> + /* This is a single step. */ >> + return record_beneath_to_wait (record_beneath_to_wait_ops, >> + ptid, status); >> + } >> + else >> + { >> + if (record_resume_step) >> + { >> + /* This is a single step. */ >> + return record_beneath_to_wait (record_beneath_to_wait_ops, >> + ptid, status); >> + } >> + else >> + { >> + /* This is not a single step. */ >> + ptid_t ret; >> + int is_breakpoint = 1; >> + CORE_ADDR pc = 0; >> + int pc_is_read = 0; >> + struct bp_location *bl; >> + struct breakpoint *b; >> + >> + do >> + { >> + ret = record_beneath_to_wait (record_beneath_to_wait_ops, >> + ptid, status); >> + >> + if (status->kind == TARGET_WAITKIND_STOPPED >> + && status->value.sig == TARGET_SIGNAL_TRAP) >> + { >> + /* Check if there is a breakpoint. */ >> + pc_is_read = 0; >> + registers_changed (); >> + for (bl = bp_location_chain; bl; bl = bl->global_next) > > This will need to be fixed. Can you use the breakpoint_here-like functions > exported by breakpoint.h instead of referencing bp_location_chain directly? > >> + { >> + b = bl->owner; >> + gdb_assert (b); >> + if (b->enable_state != bp_enabled >> + && b->enable_state != bp_permanent) >> + continue; >> + if (!pc_is_read) >> + { >> + pc = >> + regcache_read_pc (get_thread_regcache (ret)); >> + pc_is_read = 1; >> + } >> + switch (b->type) >> + { >> + default: >> + if (bl->address == pc) >> + goto breakpoint; >> + break; >> + >> + case bp_watchpoint: >> + /* XXX teawater: I still not very clear how to >> + deal with it. */ >> + goto breakpoint; >> + break; >> + >> + case bp_catchpoint: >> + gdb_assert (b->ops != NULL >> + && b->ops->breakpoint_hit != NULL); >> + if (b->ops->breakpoint_hit (b)) >> + goto breakpoint; >> + break; >> + >> + case bp_hardware_watchpoint: >> + case bp_read_watchpoint: >> + case bp_access_watchpoint: >> + if (STOPPED_BY_WATCHPOINT (0)) >> + goto breakpoint; >> + break; >> + } >> + } >> + >> + /* There is not a breakpoint. */ >> + record_message (current_gdbarch); >> + record_beneath_to_resume (record_beneath_to_resume_ops, >> + ptid, 1, >> + record_resume_siggnal); >> + continue; >> + } >> + >> + is_breakpoint = 0; >> + >> + breakpoint: >> + /* Add gdbarch_decr_pc_after_break to pc because gdb will >> + expect the pc to be at address plus decr_pc_after_break >> + when the inferior stops at a breakpoint. */ >> + if (is_breakpoint) >> + { >> + CORE_ADDR decr_pc_after_break = >> + gdbarch_decr_pc_after_break (current_gdbarch); >> + if (decr_pc_after_break) >> + { >> + if (!pc_is_read) >> + pc = >> + regcache_read_pc (get_thread_regcache (ret)); >> + regcache_write_pc (get_thread_regcache (ret), >> + pc + decr_pc_after_break); >> + } >> + } >> + >> + break; >> + } >> + while (1); >> + >> + return ret; >> + } >> + } >> + } >> + else >> + { >> + int need_dasm = 0; >> + struct regcache *regcache = get_current_regcache (); >> + int continue_flag = 1; >> + int first_record_end = 1; >> + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); >> + CORE_ADDR tmp_pc; >> + >> + status->kind = TARGET_WAITKIND_STOPPED; >> + >> + /* Check breakpoint when forward execute. */ >> + if (execution_direction == EXEC_FORWARD) >> + { >> + tmp_pc = regcache_read_pc (regcache); >> + if (breakpoint_inserted_here_p (tmp_pc)) >> + { >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: break at 0x%s.\n", >> + paddr_nz (tmp_pc)); >> + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) >> + && !record_resume_step) >> + regcache_write_pc (regcache, >> + tmp_pc + >> + gdbarch_decr_pc_after_break >> + (get_regcache_arch (regcache))); >> + goto replay_out; >> + } >> + } >> + >> + record_get_sig = 0; >> + signal (SIGINT, record_sig_handler); >> + /* If GDB is in terminal_inferior mode, it will not get the signal. >> + And in GDB replay mode, GDB doesn't need to be in terminal_inferior >> + mode, because inferior will not executed. >> + Then set it to terminal_ours to make GDB get the signal. */ >> + target_terminal_ours (); >> + >> + /* In EXEC_FORWARD mode, record_list points to the tail of prev >> + instruction. */ >> + if (execution_direction == EXEC_FORWARD && record_list->next) >> + record_list = record_list->next; >> + >> + /* Loop over the record_list, looking for the next place to >> + stop. */ >> + do >> + { >> + /* Check for beginning and end of log. */ >> + if (execution_direction == EXEC_REVERSE >> + && record_list == &record_first) >> + { >> + /* Hit beginning of record log in reverse. */ >> + status->kind = TARGET_WAITKIND_NO_HISTORY; >> + break; >> + } >> + if (execution_direction != EXEC_REVERSE && !record_list->next) >> + { >> + /* Hit end of record log going forward. */ >> + status->kind = TARGET_WAITKIND_NO_HISTORY; >> + break; >> + } >> + >> + /* Set ptid, register and memory according to record_list. */ >> + if (record_list->type == record_reg) >> + { >> + /* reg */ >> + gdb_byte reg[MAX_REGISTER_SIZE]; >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_reg %s to " >> + "inferior num = %d.\n", >> + host_address_to_string (record_list), >> + record_list->u.reg.num); >> + regcache_cooked_read (regcache, record_list->u.reg.num, reg); >> + regcache_cooked_write (regcache, record_list->u.reg.num, >> + record_list->u.reg.val); >> + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); >> + } >> + else if (record_list->type == record_mem) >> + { >> + /* mem */ >> + gdb_byte *mem = alloca (record_list->u.mem.len); >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_mem %s to " >> + "inferior addr = 0x%s len = %d.\n", >> + host_address_to_string (record_list), >> + paddr_nz (record_list->u.mem.addr), >> + record_list->u.mem.len); >> + >> + if (target_read_memory >> + (record_list->u.mem.addr, mem, record_list->u.mem.len)) >> + error (_("Process record: error reading memory at " >> + "addr = 0x%s len = %d."), >> + paddr_nz (record_list->u.mem.addr), >> + record_list->u.mem.len); >> + >> + if (target_write_memory >> + (record_list->u.mem.addr, record_list->u.mem.val, >> + record_list->u.mem.len)) >> + error (_ >> + ("Process record: error writing memory at " >> + "addr = 0x%s len = %d."), >> + paddr_nz (record_list->u.mem.addr), >> + record_list->u.mem.len); >> + >> + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); >> + } >> + else >> + { >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_end %s to " >> + "inferior need_dasm = %d.\n", >> + host_address_to_string (record_list), >> + record_list->u.need_dasm); >> + >> + if (execution_direction == EXEC_FORWARD) >> + need_dasm = record_list->u.need_dasm; >> + if (need_dasm) >> + gdbarch_process_record_dasm (current_gdbarch); >> + >> + if (first_record_end && execution_direction == EXEC_REVERSE) >> + { >> + /* When reverse excute, the first record_end is the part of >> + current instruction. */ >> + first_record_end = 0; >> + } >> + else >> + { >> + /* In EXEC_REVERSE mode, this is the record_end of prev >> + instruction. >> + In EXEC_FORWARD mode, this is the record_end of current >> + instruction. */ >> + /* step */ >> + if (record_resume_step) >> + { >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: step.\n"); >> + continue_flag = 0; >> + } >> + >> + /* check breakpoint */ >> + tmp_pc = regcache_read_pc (regcache); >> + if (breakpoint_inserted_here_p (tmp_pc)) >> + { >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: break " >> + "at 0x%s.\n", >> + paddr_nz (tmp_pc)); >> + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) >> + && execution_direction == EXEC_FORWARD >> + && !record_resume_step) >> + regcache_write_pc (regcache, >> + tmp_pc + >> + gdbarch_decr_pc_after_break >> + (get_regcache_arch (regcache))); >> + continue_flag = 0; >> + } >> + } >> + if (execution_direction == EXEC_REVERSE) >> + need_dasm = record_list->u.need_dasm; >> + } >> + >> +next: >> + if (continue_flag) >> + { >> + if (execution_direction == EXEC_REVERSE) >> + { >> + if (record_list->prev) >> + record_list = record_list->prev; >> + } >> + else >> + { >> + if (record_list->next) >> + record_list = record_list->next; >> + } >> + } >> + } >> + while (continue_flag); >> + >> + signal (SIGINT, handle_sigint); >> + >> +replay_out: >> + if (record_get_sig) >> + status->value.sig = TARGET_SIGNAL_INT; >> + else >> + status->value.sig = TARGET_SIGNAL_TRAP; >> + >> + discard_cleanups (old_cleanups); >> + } >> + >> + do_cleanups (set_cleanups); >> + return inferior_ptid; >> +} > > I have to say that I find that function confusing, due to > the use of gotos, and excessive nesting. Personally, I much prefer > code that does: > > if (foo) > { > /* something */ > return; > } > > if (bar) > { > /* something */ > return; > } > > if (lala) > { > /* something */ > return; > } > > Over: > > if (foo) > { > /* something */ > return; > } > else > { > if (bar) > { > /* something */ > return; > } > else > { > if (lala) > { > /* something */ > return; > } > } > } > > >> + >> +static void >> +record_disconnect (struct target_ops *target, char *args, int from_tty) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); >> + >> + unpush_target (&record_ops); >> + target_disconnect (args, from_tty); >> +} >> + >> +static void >> +record_detach (struct target_ops *ops, char *args, int from_tty) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); >> + >> + unpush_target (&record_ops); >> + target_detach (args, from_tty); >> +} > > This trick you're using happens to work, but, could you try > this instead? Here and elsewhere similarly. > > struct target_ops *beneath = find_target_beaneath (ops); > unpush_target (ops); > beneath->to_detach (args, from_tty); > >> + >> +static void >> +record_mourn_inferior (struct target_ops *ops) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: " >> + "record_mourn_inferior\n"); >> + >> + unpush_target (&record_ops); >> + target_mourn_inferior (); >> +} >> + >> +/* Close process record target before killing the inferior process. */ >> +static void >> +record_kill (void) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); >> + >> + unpush_target (&record_ops); >> + target_kill (); >> +} >> + >> +/* Record registers change (by user or by GDB) to list as an instruction. */ >> +static void >> +record_registers_change (struct regcache *regcache, int regnum) >> +{ >> + /* Check record_insn_num. */ >> + record_check_insn_num (0); >> + >> + record_arch_list_head = NULL; >> + record_arch_list_tail = NULL; >> + >> + record_regcache = get_current_regcache (); >> + >> + if (regnum < 0) >> + { >> + int i; >> + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) >> + { >> + if (record_arch_list_add_reg (i)) >> + { >> + record_list_release (record_arch_list_tail); >> + error (_("Process record: failed to record execution log.")); >> + } >> + } >> + } >> + else >> + { >> + if (record_arch_list_add_reg (regnum)) >> + { >> + record_list_release (record_arch_list_tail); >> + error (_("Process record: failed to record execution log.")); >> + } >> + } >> + if (record_arch_list_add_end (0)) >> + { >> + record_list_release (record_arch_list_tail); >> + error (_("Process record: failed to record execution log.")); >> + } >> + record_list->next = record_arch_list_head; >> + record_arch_list_head->prev = record_list; >> + record_list = record_arch_list_tail; >> + >> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >> + record_list_release_first (); >> + else >> + record_insn_num++; >> +} >> + >> +static void >> +record_store_registers (struct target_ops *ops, struct regcache *regcache, >> + int regno) >> +{ >> + if (!record_gdb_operation_disable) >> + { >> + if (RECORD_IS_REPLAY) >> + { >> + int n; >> + struct cleanup *old_cleanups; >> + >> + /* Let user choose if he wants to write register or not. */ >> + if (regno < 0) >> + n = >> + nquery (_("Because GDB is in replay mode, changing the " >> + "value of a register will make the execution " >> + "log unusable from this point onward. " >> + "Change all registers?")); >> + else >> + n = >> + nquery (_("Because GDB is in replay mode, changing the value " >> + "of a register will make the execution log unusable " >> + "from this point onward. Change register %s?"), >> + gdbarch_register_name (get_regcache_arch (regcache), >> + regno)); >> + >> + if (!n) >> + { >> + /* Invalidate the value of regcache that was set in function >> + "regcache_raw_write". */ >> + if (regno < 0) >> + { >> + int i; >> + for (i = 0; >> + i < gdbarch_num_regs (get_regcache_arch (regcache)); >> + i++) >> + regcache_invalidate (regcache, i); >> + } >> + else >> + regcache_invalidate (regcache, regno); >> + >> + error (_("Process record canceled the operation.")); >> + } >> + >> + /* Destroy the record from here forward. */ >> + record_list_release_next (); >> + } >> + >> + record_registers_change (regcache, regno); >> + } >> + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, >> + regcache, regno); >> +} >> + >> +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. >> + In replay mode, we cannot write memory unles we are willing to >> + invalidate the record/replay log from this point forward. */ >> + >> +static LONGEST >> +record_xfer_partial (struct target_ops *ops, enum target_object object, >> + const char *annex, gdb_byte * readbuf, >> + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) >> +{ >> + if (!record_gdb_operation_disable >> + && (object == TARGET_OBJECT_MEMORY >> + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) >> + { >> + if (RECORD_IS_REPLAY) >> + { >> + /* Let user choose if he wants to write memory or not. */ >> + if (!nquery (_("Because GDB is in replay mode, writing to memory " >> + "will make the execution log unusable from this " >> + "point onward. Write memory at address 0x%s?"), >> + paddr_nz (offset))) >> + return -1; >> + >> + /* Destroy the record from here forward. */ >> + record_list_release_next (); >> + } >> + >> + /* Check record_insn_num */ >> + record_check_insn_num (0); >> + >> + /* Record registers change to list as an instruction. */ >> + record_arch_list_head = NULL; >> + record_arch_list_tail = NULL; >> + if (record_arch_list_add_mem (offset, len)) >> + { >> + record_list_release (record_arch_list_tail); >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + _("Process record: failed to record " >> + "execution log.")); >> + return -1; >> + } >> + if (record_arch_list_add_end (0)) >> + { >> + record_list_release (record_arch_list_tail); >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + _("Process record: failed to record " >> + "execution log.")); >> + return -1; >> + } >> + record_list->next = record_arch_list_head; >> + record_arch_list_head->prev = record_list; >> + record_list = record_arch_list_tail; >> + >> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >> + record_list_release_first (); >> + else >> + record_insn_num++; >> + } >> + >> + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, >> + object, annex, readbuf, writebuf, >> + offset, len); >> +} >> + >> +/* record_insert_breakpoint >> + record_remove_breakpoint >> + Behavior is conditional on RECORD_IS_REPLAY. >> + We will not actually insert or remove breakpoints when replaying, >> + nor when recording. */ >> + >> +static int >> +record_insert_breakpoint (struct bp_target_info *bp_tgt) >> +{ >> + if (!RECORD_IS_REPLAY) >> + { >> + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); >> + int ret = record_beneath_to_insert_breakpoint (bp_tgt); >> + >> + do_cleanups (old_cleanups); >> + >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int >> +record_remove_breakpoint (struct bp_target_info *bp_tgt) >> +{ >> + if (!RECORD_IS_REPLAY) >> + { >> + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); >> + int ret = record_beneath_to_remove_breakpoint (bp_tgt); >> + >> + do_cleanups (old_cleanups); >> + >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int >> +record_can_execute_reverse (void) >> +{ >> + return 1; >> +} >> + >> +static void >> +init_record_ops (void) >> +{ >> + record_ops.to_shortname = "record"; >> + record_ops.to_longname = "Process record and replay target"; >> + record_ops.to_doc = >> + "Log program while executing and replay execution from log."; >> + record_ops.to_open = record_open; >> + record_ops.to_close = record_close; >> + record_ops.to_resume = record_resume; >> + record_ops.to_wait = record_wait; >> + record_ops.to_disconnect = record_disconnect; >> + record_ops.to_detach = record_detach; >> + record_ops.to_mourn_inferior = record_mourn_inferior; >> + record_ops.to_kill = record_kill; >> + record_ops.to_create_inferior = find_default_create_inferior; >> + record_ops.to_store_registers = record_store_registers; >> + record_ops.to_xfer_partial = record_xfer_partial; >> + record_ops.to_insert_breakpoint = record_insert_breakpoint; >> + record_ops.to_remove_breakpoint = record_remove_breakpoint; >> + record_ops.to_can_execute_reverse = record_can_execute_reverse; >> + record_ops.to_stratum = record_stratum; >> + record_ops.to_magic = OPS_MAGIC; >> +} >> + >> +static void >> +show_record_debug (struct ui_file *file, int from_tty, >> + struct cmd_list_element *c, const char *value) >> +{ >> + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), >> + value); >> +} >> + >> +/* cmd_record_start -- alias for "target record". */ >> + >> +static void >> +cmd_record_start (char *args, int from_tty) >> +{ >> + execute_command ("target record", from_tty); >> +} >> + >> +/* cmd_record_delete -- truncate the record log from the present point >> + of replay until the end. */ >> + >> +static void >> +cmd_record_delete (char *args, int from_tty) >> +{ >> + if (TARGET_IS_PROCESS_RECORD) >> + { >> + if (RECORD_IS_REPLAY) >> + { >> + if (!from_tty || query (_("Delete the log from this point forward " >> + "and begin to record the running message " >> + "at current PC?"))) >> + record_list_release_next (); >> + } >> + else >> + printf_unfiltered (_("Already at end of record list.\n")); >> + >> + } >> + else >> + printf_unfiltered (_("Process record is not started.\n")); >> +} >> + >> +/* cmd_record_stop -- implement the "stoprecord" command. */ >> + >> +static void >> +cmd_record_stop (char *args, int from_tty) >> +{ >> + if (TARGET_IS_PROCESS_RECORD) >> + { >> + if (!record_list || !from_tty || query (_("Delete recorded log and " >> + "stop recording?"))) >> + unpush_target (&record_ops); >> + } >> + else >> + printf_unfiltered (_("Process record is not started.\n")); >> +} >> + >> +/* set_record_insn_max_num -- set upper limit of record log size. */ >> + >> +static void >> +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) >> +{ >> + if (record_insn_num > record_insn_max_num && record_insn_max_num) >> + { >> + printf_unfiltered (_("Record instructions number is bigger than " >> + "record instructions max number. Auto delete " >> + "the first ones?\n")); >> + >> + while (record_insn_num > record_insn_max_num) >> + record_list_release_first (); >> + } >> +} >> + >> +/* show_record_insn_number -- print the current index >> + into the record log (number of insns recorded so far). */ >> + >> +static void >> +show_record_insn_number (char *ignore, int from_tty) >> +{ >> + printf_unfiltered (_("Record instruction number is %d.\n"), >> + record_insn_num); >> +} >> + >> +void >> +_initialize_record (void) >> +{ >> + /* Init record_maskall. */ >> + if (sigfillset (&record_maskall) == -1) >> + perror_with_name (_("Process record: sigfillset failed")); > > This will not build on all hosts. Is it still needed? I can't > find any other reference to this variable in this patch. > >> + >> + /* Init record_first. */ >> + record_first.prev = NULL; >> + record_first.next = NULL; >> + record_first.type = record_end; >> + record_first.u.need_dasm = 0; >> + >> + init_record_ops (); >> + add_target (&record_ops); >> + >> + add_setshow_zinteger_cmd ("record", no_class, &record_debug, >> + _("Set debugging of record/replay feature."), >> + _("Show debugging of record/replay feature."), >> + _("When enabled, debugging output for " >> + "record/replay feature is displayed."), >> + NULL, show_record_debug, &setdebuglist, >> + &showdebuglist); >> + >> + add_com ("record", class_obscure, cmd_record_start, >> + _("Abbreviated form of \"target record\" command.")); >> + >> + add_com_alias ("rec", "record", class_obscure, 1); >> + >> + /* XXX: I try to use some simple commands such as "disconnect" and >> + "detach" to support this functions. But these commands all have >> + other affect to GDB such as call function "no_shared_libraries". >> + So I add special commands to GDB. */ >> + add_com ("delrecord", class_obscure, cmd_record_delete, >> + _("Delete the rest of execution log and start recording it anew.")); >> + add_com_alias ("dr", "delrecord", class_obscure, 1); >> + add_com ("stoprecord", class_obscure, cmd_record_stop, >> + _("Stop the record/replay target.")); >> + add_com_alias ("sr", "stoprecord", class_obscure, 1); >> + >> + /* Record instructions number limit command. */ >> + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, >> + &record_stop_at_limit, >> + _("Set whether record/replay stop when " >> + "record/replay buffer becomes full."), >> + _("Show whether record/replay stop when " >> + "record/replay buffer becomes full."), >> + _("Enable is default value.\n" >> + "When enabled, if the record/replay buffer " >> + "becomes full,\n" >> + "ask user what to do.\n" >> + "When disabled, if the record/replay buffer " >> + "becomes full,\n" >> + "delete it and start new recording."), >> + NULL, NULL, &setlist, &showlist); >> + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, >> + &record_insn_max_num, >> + _("Set record/replay buffer limit."), >> + _("Show record/replay buffer limit."), >> + _("Set the maximum number of instructions to be " >> + "stored in the\n" >> + "record/replay buffer. " >> + "Zero means unlimited (default 200000)."), >> + set_record_insn_max_num, >> + NULL, &setlist, &showlist); >> + add_info ("record-insn-number", show_record_insn_number, >> + _("Show the current number of instructions in the " >> + "record/replay buffer.")); >> +} >> Index: src/gdb/record.h >> =================================================================== >> --- /dev/null 1970-01-01 00:00:00.000000000 +0000 >> +++ src/gdb/record.h 2009-02-28 20:23:20.000000000 +0000 >> @@ -0,0 +1,87 @@ >> +/* Process record and replay target for GDB, the GNU debugger. >> + >> + Copyright (C) 2008 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/>. */ >> + >> +#ifndef _RECORD_H_ >> +#define _RECORD_H_ >> + >> +#define TARGET_IS_PROCESS_RECORD \ >> + (current_target.beneath == &record_ops) > > Sorry, but I repeat the request I've made several times already. This is > not the right way to do this. You need to add a new target_ops method or > property that the core of GDB checks on. It is not correct that make > the core of GDB reference record_ops directly. To come up with > the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, > consider what is the property of the current target that GDB needs to > know about the current target. Is it something like: > > target_is_recording () ? > target_is_replaying () ? > target_is_read_only () ? > > etc. > >> +#define RECORD_IS_REPLAY \ >> + (record_list->next || execution_direction == EXEC_REVERSE) > > AFAICS, this macro is not used outside of record.c. It should move > there, along with anything that isn't used outside of record.c. > >> + >> +typedef struct record_reg_s >> +{ >> + int num; >> + gdb_byte *val; >> +} record_reg_t; >> + >> +typedef struct record_mem_s >> +{ >> + CORE_ADDR addr; >> + int len; >> + gdb_byte *val; >> +} record_mem_t; >> + >> +enum record_type >> +{ >> + record_end = 0, >> + record_reg, >> + record_mem >> +}; >> + >> +/* This is the core struct of record function. >> + >> + An entity of record_t is a record of the value change of a register >> + ("record_reg") or a part of memory ("record_mem"). And each >> + instruction must has a record_t ("record_end") that points out this >> + is the last record_t of this instruction. >> + >> + Each record_t is linked to "record_list" by "prev" and "next". >> + */ >> +typedef struct record_s >> +{ >> + struct record_s *prev; >> + struct record_s *next; >> + enum record_type type; >> + union >> + { >> + /* reg */ >> + record_reg_t reg; >> + /* mem */ >> + record_mem_t mem; >> + /* end */ >> + int need_dasm; >> + } u; >> +} record_t; >> + >> +extern int record_debug; >> +extern record_t *record_list; >> +extern record_t *record_arch_list_head; >> +extern record_t *record_arch_list_tail; >> +extern struct regcache *record_regcache; > > Most of these things don't appear to be used anywhere else other > than in record.c. Can you remove these declarations from the > public header, and make them static in record.c? > >> + >> +extern struct target_ops record_ops; > > Once you get rid of TARGET_IS_PROCESS_RECORD this doesn't > need to be public anymore. > >> + >> +extern int record_arch_list_add_reg (int num); >> +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); >> +extern int record_arch_list_add_end (int need_dasm); >> +extern void record_message (struct gdbarch *gdbarch); >> +extern struct cleanup * record_gdb_operation_disable_set (void); >> + >> +#endif /* _RECORD_H_ */ >> > > -- > Pedro Alves > [-- Attachment #2: 3-record_target.txt --] [-- Type: text/plain, Size: 37660 bytes --] --- Makefile.in | 4 record.c | 1214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ record.h | 74 +++ 3 files changed, 1290 insertions(+), 2 deletions(-) --- a/Makefile.in +++ b/Makefile.in @@ -661,7 +661,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr valarith.c valops.c valprint.c value.c varobj.c vec.c \ wrapper.c \ xml-tdesc.c xml-support.c \ - inferior.c + inferior.c record.c LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c @@ -812,7 +812,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ solib.o solib-null.o \ prologue-value.o memory-map.o xml-support.o \ target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ - inferior.o osdata.o + inferior.o osdata.o record.o TSOBS = inflow.o --- /dev/null +++ b/record.c @@ -0,0 +1,1214 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008, 2009 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "gdbcmd.h" +#include "regcache.h" +#include "gdbthread.h" +#include "event-top.h" +#include "exceptions.h" +#include "record.h" + +#include <signal.h> + +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 + +#define RECORD_IS_REPLAY \ + (record_list->next || execution_direction == EXEC_REVERSE) + +/* This is the debug switch for process record. */ +int record_debug = 0; + +/* This is regcache is used by record_message, record_arch_list_add_reg + and gdbarch_process_record to increase the speed that access to the + value of the registers. */ +struct regcache *record_regcache = NULL; + +/* These list is for execution log. */ +static record_t record_first; +static record_t *record_list = &record_first; +static record_t *record_arch_list_head = NULL; +static record_t *record_arch_list_tail = NULL; + +/* 1 ask user. 0 auto delete the last record_t. */ +static int record_stop_at_limit = 1; +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; +static int record_insn_num = 0; + +/* The target_ops of process record. */ +static struct target_ops record_ops; + +/* The beneath function pointers. */ +static struct target_ops *record_beneath_to_resume_ops; +static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal); +static struct target_ops *record_beneath_to_wait_ops; +static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *); +static struct target_ops *record_beneath_to_store_registers_ops; +static void (*record_beneath_to_store_registers) (struct target_ops *, + struct regcache *, + int regno); +static struct target_ops *record_beneath_to_xfer_partial_ops; +static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, + LONGEST len); +static int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *); +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *); + +static void +record_list_release (record_t * rec) +{ + record_t *tmp; + + if (!rec) + return; + + while (rec->next) + { + rec = rec->next; + } + + while (rec->prev) + { + tmp = rec; + rec = rec->prev; + if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + } + + if (rec != &record_first) + xfree (rec); +} + +static void +record_list_release_next (void) +{ + record_t *rec = record_list; + record_t *tmp = rec->next; + rec->next = NULL; + while (tmp) + { + rec = tmp->next; + if (tmp->type == record_reg) + record_insn_num--; + else if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + tmp = rec; + } +} + +static void +record_list_release_first (void) +{ + record_t *tmp = NULL; + enum record_type type; + + if (!record_first.next) + return; + + while (1) + { + type = record_first.next->type; + + if (type == record_reg) + xfree (record_first.next->u.reg.val); + else if (type == record_mem) + xfree (record_first.next->u.mem.val); + tmp = record_first.next; + record_first.next = tmp->next; + xfree (tmp); + + if (!record_first.next) + { + gdb_assert (record_insn_num == 1); + break; + } + + record_first.next->prev = &record_first; + + if (type == record_end) + break; + } + + record_insn_num--; +} + +/* Add a record_t to record_arch_list. */ +static void +record_arch_list_add (record_t * rec) +{ + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_arch_list_add %s.\n", + host_address_to_string (rec)); + + if (record_arch_list_tail) + { + record_arch_list_tail->next = rec; + rec->prev = record_arch_list_tail; + record_arch_list_tail = rec; + } + else + { + record_arch_list_head = rec; + record_arch_list_tail = rec; + } +} + +/* Record the value of a register NUM to record_arch_list. */ +int +record_arch_list_add_reg (int num) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add register num = %d to " + "record list.\n", + num); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + rec->u.reg.num = num; + + regcache_raw_read (record_regcache, num, rec->u.reg.val); + + record_arch_list_add (rec); + + return 0; +} + +/* Record the value of a region of memory whose address is ADDR and + length is LEN to record_arch_list. */ + +int +record_arch_list_add_mem (CORE_ADDR addr, int len) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add mem addr = 0x%s len = %d to " + "record list.\n", + paddr_nz (addr), len); + + if (!addr) + return 0; + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.mem.val = (gdb_byte *) xmalloc (len); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + rec->u.mem.addr = addr; + rec->u.mem.len = len; + + if (target_read_memory (addr, rec->u.mem.val, len)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: error reading memory at " + "addr = 0x%s len = %d.\n", + paddr_nz (addr), len); + xfree (rec->u.mem.val); + xfree (rec); + return -1; + } + + record_arch_list_add (rec); + + return 0; +} + +/* Add a record_end type record_t to record_arch_list. */ +int +record_arch_list_add_end (void) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add end to arch list.\n"); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + + record_arch_list_add (rec); + + return 0; +} + +static void +record_check_insn_num (int set_terminal) +{ + if (record_insn_max_num) + { + gdb_assert (record_insn_num <= record_insn_max_num); + if (record_insn_num == record_insn_max_num) + { + /* Ask user what to do. */ + if (record_stop_at_limit) + { + int q; + if (set_terminal) + target_terminal_ours (); + q = yquery (_("Do you want to auto delete previous execution " + "log entries when record/replay buffer becomes " + "full (record-stop-at-limit)?")); + if (set_terminal) + target_terminal_inferior (); + if (q) + record_stop_at_limit = 0; + else + error (_("Process record: inferior program stopped.")); + } + } + } +} + +/* 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_list_release (record_arch_list_tail); +} + +static int +record_message (void *args) +{ + int ret; + struct gdbarch *gdbarch = args; + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + /* Check record_insn_num. */ + record_check_insn_num (1); + + record_regcache = get_current_regcache (); + + ret = gdbarch_process_record (gdbarch, + regcache_read_pc (record_regcache)); + if (ret > 0) + error (_("Process record: inferior program stopped.")); + if (ret < 0) + error (_("Process record: failed to record execution log.")); + + discard_cleanups (old_cleanups); + + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; + + return 1; +} + +static int +do_record_message (struct gdbarch *gdbarch) +{ + return catch_errors (record_message, gdbarch, NULL, RETURN_MASK_ALL); +} + +/* Set to 1 if record_store_registers and record_xfer_partial + doesn't need record. */ + +static int record_gdb_operation_disable = 0; + +struct cleanup * +record_gdb_operation_disable_set (void) +{ + struct cleanup *old_cleanups; + + record_gdb_operation_disable = 0; + old_cleanups = + make_cleanup_restore_integer (&record_gdb_operation_disable); + record_gdb_operation_disable = 1; + + return old_cleanups; +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + + /* check exec */ + if (!target_has_execution) + error (_("Process record: the program is not being run.")); + if (non_stop) + error (_("Process record target can't debug inferior in non-stop mode " + "(non-stop).")); + if (target_async_permitted) + error (_("Process record target can't debug inferior in asynchronous " + "mode (target-async).")); + + if (!gdbarch_process_record_p (current_gdbarch)) + error (_("Process record: the current architecture doesn't support " + "record function.")); + + /* Check if record target is already running. */ + if (current_target.to_stratum == record_stratum) + { + if (!nquery + (_("Process record target already running, do you want to delete " + "the old record log?"))) + return; + } + + /*Reset the beneath function pointers. */ + record_beneath_to_resume = NULL; + record_beneath_to_wait = NULL; + record_beneath_to_store_registers = NULL; + record_beneath_to_xfer_partial = NULL; + record_beneath_to_insert_breakpoint = NULL; + record_beneath_to_remove_breakpoint = NULL; + + /* Set the beneath function pointers. */ + for (t = current_target.beneath; t != NULL; t = t->beneath) + { + if (!record_beneath_to_resume) + { + record_beneath_to_resume = t->to_resume; + record_beneath_to_resume_ops = t; + } + if (!record_beneath_to_wait) + { + record_beneath_to_wait = t->to_wait; + record_beneath_to_wait_ops = t; + } + if (!record_beneath_to_store_registers) + { + record_beneath_to_store_registers = t->to_store_registers; + record_beneath_to_store_registers_ops = t; + } + if (!record_beneath_to_xfer_partial) + { + record_beneath_to_xfer_partial = t->to_xfer_partial; + record_beneath_to_xfer_partial_ops = t; + } + if (!record_beneath_to_insert_breakpoint) + record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; + if (!record_beneath_to_remove_breakpoint) + record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; + } + if (!record_beneath_to_resume) + error (_("Process record can't get to_resume.")); + if (!record_beneath_to_wait) + error (_("Process record can't get to_wait.")); + if (!record_beneath_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!record_beneath_to_xfer_partial) + error (_("Process record can't get to_xfer_partial.")); + if (!record_beneath_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!record_beneath_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); + + /* Reset */ + record_insn_num = 0; + record_list = &record_first; + record_list->next = NULL; +} + +static void +record_close (int quitting) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); + + record_list_release (record_list); +} + +static int record_resume_step = 0; +static enum target_signal record_resume_siggnal; +static int record_resume_error; + +static void +record_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; + + if (!RECORD_IS_REPLAY) + { + if (do_record_message (current_gdbarch)) + { + record_resume_error = 0; + } + else + { + record_resume_error = 1; + return; + } + record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, + siggnal); + } +} + +static int record_get_sig = 0; + +static void +record_sig_handler (int signo) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); + + /* It will break the running inferior in replay mode. */ + record_resume_step = 1; + + /* It will let record_wait set inferior status to get the signal + SIGINT. */ + record_get_sig = 1; +} + +static void +record_wait_cleanups (void *ignore) +{ + if (execution_direction == EXEC_REVERSE) + { + if (record_list->next) + record_list = record_list->next; + } + else + record_list = record_list->prev; +} + +/* In replay mode, this function examines the recorded log and + determines where to stop. */ + +static ptid_t +record_wait (struct target_ops *ops, + ptid_t ptid, struct target_waitstatus *status) +{ + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_wait " + "record_resume_step = %d\n", + record_resume_step); + + if (!RECORD_IS_REPLAY) + { + if (record_resume_error) + { + /* If record_resume get error, return directly. */ + status->kind = TARGET_WAITKIND_STOPPED; + status->value.sig = TARGET_SIGNAL_TRAP; + return inferior_ptid; + } + + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (record_beneath_to_wait_ops, + ptid, status); + } + else + { + /* This is not a single step. */ + ptid_t ret; + CORE_ADDR tmp_pc; + + while (1) + { + ret = + record_beneath_to_wait (record_beneath_to_wait_ops, ptid, + status); + + if (status->kind == TARGET_WAITKIND_STOPPED + && status->value.sig == TARGET_SIGNAL_TRAP) + { + /* Check if there is a breakpoint. */ + registers_changed (); + tmp_pc = read_pc (); + if (breakpoint_inserted_here_p (tmp_pc)) + { + /* There is a breakpoint. */ + CORE_ADDR decr_pc_after_break = + gdbarch_decr_pc_after_break (current_gdbarch); + if (decr_pc_after_break) + { + regcache_write_pc (get_thread_regcache (ret), + tmp_pc + decr_pc_after_break); + } + } + else + { + /* There is not a breakpoint. */ + if (!do_record_message (current_gdbarch)) + { + break; + } + record_beneath_to_resume (record_beneath_to_resume_ops, + ptid, 1, + record_resume_siggnal); + continue; + } + } + + /* The inferior is broken by a breakpoint or a signal. */ + break; + } + + return ret; + } + } + else + { + struct regcache *regcache = get_current_regcache (); + int continue_flag = 1; + int first_record_end = 1; + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); + CORE_ADDR tmp_pc; + + status->kind = TARGET_WAITKIND_STOPPED; + + /* Check breakpoint when forward execute. */ + if (execution_direction == EXEC_FORWARD) + { + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + goto replay_out; + } + } + + record_get_sig = 0; + signal (SIGINT, record_sig_handler); + /* If GDB is in terminal_inferior mode, it will not get the signal. + And in GDB replay mode, GDB doesn't need to be in terminal_inferior + mode, because inferior will not executed. + Then set it to terminal_ours to make GDB get the signal. */ + target_terminal_ours (); + + /* In EXEC_FORWARD mode, record_list points to the tail of prev + instruction. */ + if (execution_direction == EXEC_FORWARD && record_list->next) + record_list = record_list->next; + + /* Loop over the record_list, looking for the next place to + stop. */ + do + { + /* Check for beginning and end of log. */ + if (execution_direction == EXEC_REVERSE + && record_list == &record_first) + { + /* Hit beginning of record log in reverse. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + if (execution_direction != EXEC_REVERSE && !record_list->next) + { + /* Hit end of record log going forward. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + + /* Set ptid, register and memory according to record_list. */ + if (record_list->type == record_reg) + { + /* reg */ + gdb_byte reg[MAX_REGISTER_SIZE]; + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (record_list), + record_list->u.reg.num); + regcache_cooked_read (regcache, record_list->u.reg.num, reg); + regcache_cooked_write (regcache, record_list->u.reg.num, + record_list->u.reg.val); + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); + } + else if (record_list->type == record_mem) + { + /* mem */ + gdb_byte *mem = alloca (record_list->u.mem.len); + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = 0x%s len = %d.\n", + host_address_to_string (record_list), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory + (record_list->u.mem.addr, mem, record_list->u.mem.len)) + error (_("Process record: error reading memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_write_memory + (record_list->u.mem.addr, record_list->u.mem.val, + record_list->u.mem.len)) + error (_ + ("Process record: error writing memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); + } + else + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_end %s to " + "inferior.\n", + host_address_to_string (record_list)); + + if (first_record_end && execution_direction == EXEC_REVERSE) + { + /* When reverse excute, the first record_end is the part of + current instruction. */ + first_record_end = 0; + } + else + { + /* In EXEC_REVERSE mode, this is the record_end of prev + instruction. + In EXEC_FORWARD mode, this is the record_end of current + instruction. */ + /* step */ + if (record_resume_step) + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: step.\n"); + continue_flag = 0; + } + + /* check breakpoint */ + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break " + "at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && execution_direction == EXEC_FORWARD + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + continue_flag = 0; + } + } + } + +next: + if (continue_flag) + { + if (execution_direction == EXEC_REVERSE) + { + if (record_list->prev) + record_list = record_list->prev; + } + else + { + if (record_list->next) + record_list = record_list->next; + } + } + } + while (continue_flag); + + signal (SIGINT, handle_sigint); + +replay_out: + if (record_get_sig) + status->value.sig = TARGET_SIGNAL_INT; + else + status->value.sig = TARGET_SIGNAL_TRAP; + + discard_cleanups (old_cleanups); + } + + do_cleanups (set_cleanups); + return inferior_ptid; +} + +static void +record_disconnect (struct target_ops *target, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); + + unpush_target (&record_ops); + target_disconnect (args, from_tty); +} + +static void +record_detach (struct target_ops *ops, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); + + unpush_target (&record_ops); + target_detach (args, from_tty); +} + +static void +record_mourn_inferior (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: " + "record_mourn_inferior\n"); + + unpush_target (&record_ops); + target_mourn_inferior (); +} + +/* Close process record target before killing the inferior process. */ +static void +record_kill (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); + + unpush_target (&record_ops); + target_kill (); +} + +/* Record registers change (by user or by GDB) to list as an instruction. */ +static void +record_registers_change (struct regcache *regcache, int regnum) +{ + /* Check record_insn_num. */ + record_check_insn_num (0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + record_regcache = get_current_regcache (); + + if (regnum < 0) + { + int i; + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) + { + if (record_arch_list_add_reg (i)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + } + else + { + if (record_arch_list_add_reg (regnum)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + if (record_arch_list_add_end ()) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +static void +record_store_registers (struct target_ops *ops, struct regcache *regcache, + int regno) +{ + if (!record_gdb_operation_disable) + { + if (RECORD_IS_REPLAY) + { + int n; + struct cleanup *old_cleanups; + + /* Let user choose if he wants to write register or not. */ + if (regno < 0) + n = + nquery (_("Because GDB is in replay mode, changing the " + "value of a register will make the execution " + "log unusable from this point onward. " + "Change all registers?")); + else + n = + nquery (_("Because GDB is in replay mode, changing the value " + "of a register will make the execution log unusable " + "from this point onward. Change register %s?"), + gdbarch_register_name (get_regcache_arch (regcache), + regno)); + + if (!n) + { + /* Invalidate the value of regcache that was set in function + "regcache_raw_write". */ + if (regno < 0) + { + int i; + for (i = 0; + i < gdbarch_num_regs (get_regcache_arch (regcache)); + i++) + regcache_invalidate (regcache, i); + } + else + regcache_invalidate (regcache, regno); + + error (_("Process record canceled the operation.")); + } + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + record_registers_change (regcache, regno); + } + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, + regcache, regno); +} + +/* Behavior is conditional on RECORD_IS_REPLAY. + In replay mode, we cannot write memory unles we are willing to + invalidate the record/replay log from this point forward. */ + +static LONGEST +record_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte * readbuf, + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) +{ + if (!record_gdb_operation_disable + && (object == TARGET_OBJECT_MEMORY + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + { + if (RECORD_IS_REPLAY) + { + /* Let user choose if he wants to write memory or not. */ + if (!nquery (_("Because GDB is in replay mode, writing to memory " + "will make the execution log unusable from this " + "point onward. Write memory at address 0x%s?"), + paddr_nz (offset))) + return -1; + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + /* Check record_insn_num */ + record_check_insn_num (0); + + /* Record registers change to list as an instruction. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + if (record_arch_list_add_mem (offset, len)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + if (record_arch_list_add_end ()) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +/* Behavior is conditional on RECORD_IS_REPLAY. + We will not actually insert or remove breakpoints when replaying, + nor when recording. */ + +static int +record_insert_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_insert_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_remove_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_remove_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_can_execute_reverse (void) +{ + return 1; +} + +static void +init_record_ops (void) +{ + record_ops.to_shortname = "record"; + record_ops.to_longname = "Process record and replay target"; + record_ops.to_doc = + "Log program while executing and replay execution from log."; + record_ops.to_open = record_open; + record_ops.to_close = record_close; + record_ops.to_resume = record_resume; + record_ops.to_wait = record_wait; + record_ops.to_disconnect = record_disconnect; + record_ops.to_detach = record_detach; + record_ops.to_mourn_inferior = record_mourn_inferior; + record_ops.to_kill = record_kill; + record_ops.to_create_inferior = find_default_create_inferior; + record_ops.to_store_registers = record_store_registers; + record_ops.to_xfer_partial = record_xfer_partial; + record_ops.to_insert_breakpoint = record_insert_breakpoint; + record_ops.to_remove_breakpoint = record_remove_breakpoint; + record_ops.to_can_execute_reverse = record_can_execute_reverse; + record_ops.to_stratum = record_stratum; + record_ops.to_magic = OPS_MAGIC; +} + +static void +show_record_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), + value); +} + +/* Alias for "target record". */ + +static void +cmd_record_start (char *args, int from_tty) +{ + execute_command ("target record", from_tty); +} + +/* Truncate the record log from the present point + of replay until the end. */ + +static void +cmd_record_delete (char *args, int from_tty) +{ + if (current_target.to_stratum == record_stratum) + { + if (RECORD_IS_REPLAY) + { + if (!from_tty || query (_("Delete the log from this point forward " + "and begin to record the running message " + "at current PC?"))) + record_list_release_next (); + } + else + printf_unfiltered (_("Already at end of record list.\n")); + + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* Implement the "stoprecord" command. */ + +static void +cmd_record_stop (char *args, int from_tty) +{ + if (current_target.to_stratum == record_stratum) + { + if (!record_list || !from_tty || query (_("Delete recorded log and " + "stop recording?"))) + unpush_target (&record_ops); + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* Set upper limit of record log size. */ + +static void +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) +{ + if (record_insn_num > record_insn_max_num && record_insn_max_num) + { + printf_unfiltered (_("Record instructions number is bigger than " + "record instructions max number. Auto delete " + "the first ones?\n")); + + while (record_insn_num > record_insn_max_num) + record_list_release_first (); + } +} + +/* Print the current index into the record log (number of insns recorded + so far). */ + +static void +show_record_insn_number (char *ignore, int from_tty) +{ + printf_unfiltered (_("Record instruction number is %d.\n"), + record_insn_num); +} + +void +_initialize_record (void) +{ + /* Init record_first. */ + record_first.prev = NULL; + record_first.next = NULL; + record_first.type = record_end; + + init_record_ops (); + add_target (&record_ops); + + add_setshow_zinteger_cmd ("record", no_class, &record_debug, + _("Set debugging of record/replay feature."), + _("Show debugging of record/replay feature."), + _("When enabled, debugging output for " + "record/replay feature is displayed."), + NULL, show_record_debug, &setdebuglist, + &showdebuglist); + + add_com ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command.")); + + add_com_alias ("rec", "record", class_obscure, 1); + + /* XXX: I try to use some simple commands such as "disconnect" and + "detach" to support this functions. But these commands all have + other affect to GDB such as call function "no_shared_libraries". + So I add special commands to GDB. */ + add_com ("delrecord", class_obscure, cmd_record_delete, + _("Delete the rest of execution log and start recording it anew.")); + add_com_alias ("dr", "delrecord", class_obscure, 1); + add_com ("stoprecord", class_obscure, cmd_record_stop, + _("Stop the record/replay target.")); + add_com_alias ("sr", "stoprecord", class_obscure, 1); + + /* Record instructions number limit command. */ + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, + &record_stop_at_limit, + _("Set whether record/replay stop when " + "record/replay buffer becomes full."), + _("Show whether record/replay stop when " + "record/replay buffer becomes full."), + _("Enable is default value.\n" + "When enabled, if the record/replay buffer " + "becomes full,\n" + "ask user what to do.\n" + "When disabled, if the record/replay buffer " + "becomes full,\n" + "delete it and start new recording."), + NULL, NULL, &setlist, &showlist); + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, + &record_insn_max_num, + _("Set record/replay buffer limit."), + _("Show record/replay buffer limit."), + _("Set the maximum number of instructions to be " + "stored in the\n" + "record/replay buffer. " + "Zero means unlimited (default 200000)."), + set_record_insn_max_num, + NULL, &setlist, &showlist); + add_info ("record-insn-number", show_record_insn_number, + _("Show the current number of instructions in the " + "record/replay buffer.")); +} --- /dev/null +++ b/record.h @@ -0,0 +1,74 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008, 2009 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef _RECORD_H_ +#define _RECORD_H_ + +typedef struct record_reg_s +{ + int num; + gdb_byte *val; +} record_reg_t; + +typedef struct record_mem_s +{ + CORE_ADDR addr; + int len; + gdb_byte *val; +} record_mem_t; + +enum record_type +{ + record_end = 0, + record_reg, + record_mem +}; + +/* This is the core struct of record function. + + An entity of record_t is a record of the value change of a register + ("record_reg") or a part of memory ("record_mem"). And each + instruction must has a record_t ("record_end") that points out this + is the last record_t of this instruction. + + Each record_t is linked to "record_list" by "prev" and "next". + */ +typedef struct record_s +{ + struct record_s *prev; + struct record_s *next; + enum record_type type; + union + { + /* reg */ + record_reg_t reg; + /* mem */ + record_mem_t mem; + } u; +} record_t; + +extern int record_debug; +extern struct regcache *record_regcache; + +extern int record_arch_list_add_reg (int num); +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); +extern int record_arch_list_add_end (void); +extern struct cleanup * record_gdb_operation_disable_set (void); + +#endif /* _RECORD_H_ */ ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-03-18 13:14 ` teawater @ 2009-03-18 13:54 ` teawater 0 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-03-18 13:54 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches, Marc Khouzam, Michael Snyder [-- Attachment #1: Type: text/plain, Size: 58197 bytes --] This is the patches updated follow cvs head. Thanks, Hui On Wed, Mar 18, 2009 at 21:11, teawater <teawater@gmail.com> wrote: > Hi Pedro, > > I make a new patch according to your mail. > Please help me review it. > > Thanks, > Hui > > On Tue, Mar 10, 2009 at 04:34, Pedro Alves <pedro@codesourcery.com> wrote: >> On Monday 23 February 2009 09:20:13, teawater wrote: >>> This is the new version patches that follow cvs-head. >> >> Thanks. >> >>> >>> gdb/Makefile.in | 4 >>> gdb/record.c | 1283 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ >>> gdb/record.h | 87 +++ >>> 3 files changed, 1372 insertions(+), 2 deletions(-) >>> >>> Index: src/gdb/Makefile.in >>> =================================================================== >>> --- src.orig/gdb/Makefile.in 2009-02-21 15:53:27.000000000 +0000 >>> +++ src/gdb/Makefile.in 2009-02-28 20:23:20.000000000 +0000 >>> @@ -662,7 +662,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr >>> valarith.c valops.c valprint.c value.c varobj.c vec.c \ >>> wrapper.c \ >>> xml-tdesc.c xml-support.c \ >>> - inferior.c >>> + inferior.c record.c >>> >>> LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c >>> >>> @@ -813,7 +813,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ >>> solib.o solib-null.o \ >>> prologue-value.o memory-map.o xml-support.o \ >>> target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ >>> - inferior.o osdata.o >>> + inferior.o osdata.o record.o >>> >>> TSOBS = inflow.o >>> >>> Index: src/gdb/record.c >>> =================================================================== >>> --- /dev/null 1970-01-01 00:00:00.000000000 +0000 >>> +++ src/gdb/record.c 2009-02-28 20:23:20.000000000 +0000 >>> @@ -0,0 +1,1283 @@ >>> +/* Process record and replay target for GDB, the GNU debugger. >>> + >>> + Copyright (C) 2008 Free Software Foundation, Inc. >> >> Oops, you'll have to update this. >> >>> + >>> + This file is part of GDB. >>> + >>> + This program is free software; you can redistribute it and/or modify >>> + it under the terms of the GNU General Public License as published by >>> + the Free Software Foundation; either version 3 of the License, or >>> + (at your option) any later version. >>> + >>> + This program is distributed in the hope that it will be useful, >>> + but WITHOUT ANY WARRANTY; without even the implied warranty of >>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >>> + GNU General Public License for more details. >>> + >>> + You should have received a copy of the GNU General Public License >>> + along with this program. If not, see <http://www.gnu.org/licenses/>. */ >>> + >>> +#include "defs.h" >>> +#include "target.h" >>> +#include "gdbcmd.h" >>> +#include "regcache.h" >>> +#include "inferior.h" >>> +#include "gdbthread.h" >>> +#include "event-top.h" >>> +#include "annotate.h" >>> +#include "observer.h" >>> +#include "record.h" >> >> Why did you need annotate.h? Can you check which headers are >> really needed here? >> >>> + >>> +#include <signal.h> >>> + >>> +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 >>> + >>> +int record_debug = 0; >>> + >>> +record_t record_first; >>> +record_t *record_list = &record_first; >>> +record_t *record_arch_list_head = NULL; >>> +record_t *record_arch_list_tail = NULL; >>> +struct regcache *record_regcache = NULL; >>> + >>> +/* 1 ask user. 0 auto delete the last record_t. */ >> ^ double-space please. >> >>> +static int record_stop_at_limit = 1; >>> +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; >>> +static int record_insn_num = 0; >>> + >>> +struct target_ops record_ops; >>> +static int record_resume_step = 0; >>> +static enum target_signal record_resume_siggnal; >>> +static int record_get_sig = 0; >>> +static sigset_t record_maskall; >>> +static int record_gdb_operation_disable = 0; >>> +int record_will_store_registers = 0; >> >> You've got a bunch of magic variables here. Could you >> add some comments explaining what they're for? >> >>> + >>> +extern struct bp_location *bp_location_chain; >> >> This is not right. You're accessing a breakpoints.c >> internal implementation detail. We need to come up with interfaces >> to replace this hack. >> >>> + >>> +/* The beneath function pointers. */ >>> +static struct target_ops *record_beneath_to_resume_ops = NULL; >>> +static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, >>> + enum target_signal) = NULL; >>> +static struct target_ops *record_beneath_to_wait_ops = NULL; >>> +static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, >>> + struct target_waitstatus *) = NULL; >>> +static struct target_ops *record_beneath_to_store_registers_ops = NULL; >>> +static void (*record_beneath_to_store_registers) (struct target_ops *, >>> + struct regcache *, >>> + int regno) = NULL; >>> +static struct target_ops *record_beneath_to_xfer_partial_ops = NULL; >>> +static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, >>> + enum target_object object, >>> + const char *annex, >>> + gdb_byte * readbuf, >>> + const gdb_byte * writebuf, >>> + ULONGEST offset, >>> + LONGEST len) = NULL; >>> +static int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *) = >>> + NULL; >>> +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *) = >>> + NULL; >> >> I can't say I'm happy with this mechanism yet, but it is much >> better than the previous version of making target.c aware of record.c's callbacks. >> >> >>> + >>> +static void >>> +record_list_release (record_t * rec) >> >> ^ no space after *, please. >> >>> +{ >>> + record_t *tmp; >>> + >>> + if (!rec) >>> + return; >>> + >>> + while (rec->next) >>> + { >>> + rec = rec->next; >>> + } >>> + >>> + while (rec->prev) >>> + { >>> + tmp = rec; >>> + rec = rec->prev; >>> + if (tmp->type == record_reg) >>> + xfree (tmp->u.reg.val); >>> + else if (tmp->type == record_mem) >>> + xfree (tmp->u.mem.val); >>> + xfree (tmp); >>> + } >>> + >>> + if (rec != &record_first) >>> + xfree (rec); >>> +} >>> + >>> +static void >>> +record_list_release_next (void) >>> +{ >>> + record_t *rec = record_list; >>> + record_t *tmp = rec->next; >>> + rec->next = NULL; >>> + while (tmp) >>> + { >>> + rec = tmp->next; >>> + if (tmp->type == record_reg) >>> + record_insn_num--; >>> + else if (tmp->type == record_reg) >>> + xfree (tmp->u.reg.val); >>> + else if (tmp->type == record_mem) >>> + xfree (tmp->u.mem.val); >>> + xfree (tmp); >>> + tmp = rec; >>> + } >>> +} >>> + >>> +static void >>> +record_list_release_first (void) >>> +{ >>> + record_t *tmp = NULL; >>> + enum record_type type; >>> + >>> + if (!record_first.next) >>> + return; >>> + >>> + while (1) >>> + { >>> + type = record_first.next->type; >>> + >>> + if (type == record_reg) >>> + xfree (record_first.next->u.reg.val); >>> + else if (type == record_mem) >>> + xfree (record_first.next->u.mem.val); >>> + tmp = record_first.next; >>> + record_first.next = tmp->next; >>> + xfree (tmp); >>> + >>> + if (!record_first.next) >>> + { >>> + gdb_assert (record_insn_num == 1); >>> + break; >>> + } >>> + >>> + record_first.next->prev = &record_first; >>> + >>> + if (type == record_end) >>> + break; >>> + } >>> + >>> + record_insn_num--; >>> +} >>> + >>> +/* Add a record_t to record_arch_list. */ >>> +static void >>> +record_arch_list_add (record_t * rec) >>> +{ >>> + if (record_debug > 1) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: record_arch_list_add %s.\n", >>> + host_address_to_string (rec)); >>> + >>> + if (record_arch_list_tail) >>> + { >>> + record_arch_list_tail->next = rec; >>> + rec->prev = record_arch_list_tail; >>> + record_arch_list_tail = rec; >>> + } >>> + else >>> + { >>> + record_arch_list_head = rec; >>> + record_arch_list_tail = rec; >>> + } >>> +} >>> + >>> +/* Record the value of a register ("num") to record_arch_list. */ >> >> When we want to mention the value of a parameter, we mentions it in >> uppercase, like so: >> >> /* Record the value of register NUM to record_arch_list. */ >> >> Also, there should be an empty line between the comment and >> the function itself. Here and elsewhere. >> >>> +int >>> +record_arch_list_add_reg (int num) >>> +{ >>> + record_t *rec; >>> + >>> + if (record_debug > 1) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: add register num = %d to " >>> + "record list.\n", >>> + num); >>> + >>> + rec = (record_t *) xmalloc (sizeof (record_t)); >>> + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); >>> + rec->prev = NULL; >>> + rec->next = NULL; >>> + rec->type = record_reg; >>> + rec->u.reg.num = num; >>> + >>> + regcache_raw_read (record_regcache, num, rec->u.reg.val); >>> + >>> + record_arch_list_add (rec); >>> + >>> + return 0; >>> +} >>> + >>> +/* Record the value of a region of memory whose address is "addr" and >>> + length is "len" to record_arch_list. */ >> >> /* Record the value of a region of memory whose address is ADDR and >> length is LEN to record_arch_list. */ >> >>> + >>> +int >>> +record_arch_list_add_mem (CORE_ADDR addr, int len) >>> +{ >>> + record_t *rec; >>> + >>> + if (record_debug > 1) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: add mem addr = 0x%s len = %d to " >>> + "record list.\n", >>> + paddr_nz (addr), len); >>> + >> >>> + if (!addr) >>> + return 0; >> >> Why is this check for addr == 0 necessary here? >> >>> + >>> + rec = (record_t *) xmalloc (sizeof (record_t)); >>> + rec->u.mem.val = (gdb_byte *) xmalloc (len); >>> + rec->prev = NULL; >>> + rec->next = NULL; >>> + rec->type = record_mem; >>> + rec->u.mem.addr = addr; >>> + rec->u.mem.len = len; >>> + >>> + if (target_read_memory (addr, rec->u.mem.val, len)) >>> + { >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: error reading memory at " >>> + "addr = 0x%s len = %d.\n", >>> + paddr_nz (addr), len); >>> + xfree (rec->u.mem.val); >>> + xfree (rec); >>> + return -1; >>> + } >>> + >>> + record_arch_list_add (rec); >>> + >>> + return 0; >>> +} >>> + >>> +/* Add a record_end type record_t to record_arch_list. */ >>> +int >>> +record_arch_list_add_end (int need_dasm) >>> +{ >>> + record_t *rec; >>> + >>> + if (record_debug > 1) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: add end need_dasm = %d to " >>> + "arch list.\n", >>> + need_dasm); >>> + >>> + rec = (record_t *) xmalloc (sizeof (record_t)); >>> + rec->prev = NULL; >>> + rec->next = NULL; >>> + rec->type = record_end; >>> + >>> + rec->u.need_dasm = need_dasm; >>> + >>> + record_arch_list_add (rec); >>> + >>> + return 0; >>> +} >>> + >>> +static void >>> +record_check_insn_num (int set_terminal) >>> +{ >>> + if (record_insn_max_num) >>> + { >>> + gdb_assert (record_insn_num <= record_insn_max_num); >>> + if (record_insn_num == record_insn_max_num) >>> + { >>> + /* Ask user what to do. */ >>> + if (record_stop_at_limit) >>> + { >>> + int q; >>> + if (set_terminal) >>> + target_terminal_ours (); >>> + q = yquery (_("Do you want to auto delete previous execution " >>> + "log entries when record/replay buffer becomes " >>> + "full (record-stop-at-limit)?")); >>> + if (set_terminal) >>> + target_terminal_inferior (); >>> + if (q) >>> + record_stop_at_limit = 0; >>> + else >>> + error (_("Process record: inferior program stopped.")); >>> + } >>> + } >>> + } >>> +} >>> + >>> +static void >>> +record_normal_stop (void) >>> +{ >>> + finish_thread_state (minus_one_ptid); >>> + >>> + if (!breakpoints_always_inserted_mode () && target_has_execution) >>> + remove_breakpoints (); >>> + >>> + target_terminal_ours (); >>> + >>> + if (target_has_stack && !stop_stack_dummy) >>> + set_current_sal_from_frame (get_current_frame (), 1); >>> + >>> + select_frame (get_current_frame ()); >>> + >>> + annotate_stopped (); >>> + if (!suppress_stop_observer) >>> + { >>> + if (!ptid_equal (inferior_ptid, null_ptid)) >>> + observer_notify_normal_stop (inferior_thread ()->stop_bpstat, 0); >>> + else >>> + observer_notify_normal_stop (NULL, 0); >>> + } >>> +} >> >> Nope sorry, this is worse than before. When I asked to not call >> normal_stop, I didn't mean that you should inline chunks of >> it here. This is papering over a different problem. Why >> is it that record.c needs to do this, while other targets do not? >> If we need to do something like this, it should be needed by all >> targets that can throw from target_wait (which, is documented >> as something you shouldn't do anyway). Actually, do you still need >> any of this in current head? >> >>> + >>> +/* 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_list_release (record_arch_list_tail); >>> + set_executing (inferior_ptid, 0); >>> + record_normal_stop (); >>> +} >> >> Same as above. This isn't a record.c problem. >> >>> + >>> +void >>> +record_message (struct gdbarch *gdbarch) >>> +{ >>> + int ret; >>> + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); >>> + >>> + record_arch_list_head = NULL; >>> + record_arch_list_tail = NULL; >>> + >>> + /* Check record_insn_num. */ >>> + record_check_insn_num (1); >>> + >>> + record_regcache = get_current_regcache (); >>> + >>> + ret = gdbarch_process_record (gdbarch, >>> + regcache_read_pc (record_regcache)); >>> + if (ret > 0) >>> + error (_("Process record: inferior program stopped.")); >>> + if (ret < 0) >>> + error (_("Process record: failed to record execution log.")); >>> + >>> + discard_cleanups (old_cleanups); >>> + >>> + record_list->next = record_arch_list_head; >>> + record_arch_list_head->prev = record_list; >>> + record_list = record_arch_list_tail; >>> + >>> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >>> + record_list_release_first (); >>> + else >>> + record_insn_num++; >>> +} >>> + >> >> >>> +/* Things to clean up if we error or QUIT out of function that set >>> + record_gdb_operation_disable (ie. command that caused target_wait to >>> + be called). */ >>> +static void >>> +record_gdb_operation_disable_cleanups (void *ignore) >>> +{ >>> + record_gdb_operation_disable = 0; >>> +} >>> + >>> +struct cleanup * >>> +record_gdb_operation_disable_set (void) >>> +{ >>> + struct cleanup *old_cleanups = >>> + make_cleanup (record_gdb_operation_disable_cleanups, 0); >>> + record_gdb_operation_disable = 1; >>> + >>> + return old_cleanups; >>> +} >> >> This is make_cleanup_restore_integer. >> >>> + >>> +static void >>> +record_open (char *name, int from_tty) >>> +{ >>> + struct target_ops *t; >>> + >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); >>> + >>> + /* check exec */ >> >> and core. >> >>> + if (!target_has_execution) >>> + error (_("Process record: the program is not being run.")); >>> + if (non_stop) >>> + error (_("Process record target can't debug inferior in non-stop mode " >>> + "(non-stop).")); >>> + if (target_async_permitted) >>> + error (_("Process record target can't debug inferior in asynchronous " >>> + "mode (target-async).")); >>> + >>> + if (!gdbarch_process_record_p (current_gdbarch)) >>> + error (_("Process record: the current architecture doesn't support " >>> + "record function.")); >>> + >>> + /* Check if record target is already running. */ >>> + if (TARGET_IS_PROCESS_RECORD) >>> + { >>> + if (!nquery >>> + (_("Process record target already running, do you want to delete " >>> + "the old record log?"))) >>> + return; >>> + } >>> + >>> + /* Set the beneath function pointers. */ >>> + for (t = current_target.beneath; t != NULL; t = t->beneath) >>> + { >>> + if (!record_beneath_to_resume) >>> + { >>> + record_beneath_to_resume = t->to_resume; >>> + record_beneath_to_resume_ops = t; >>> + } >>> + if (!record_beneath_to_wait) >>> + { >>> + record_beneath_to_wait = t->to_wait; >>> + record_beneath_to_wait_ops = t; >>> + } >>> + if (!record_beneath_to_store_registers) >>> + { >>> + record_beneath_to_store_registers = t->to_store_registers; >>> + record_beneath_to_store_registers_ops = t; >>> + } >>> + if (!record_beneath_to_xfer_partial) >>> + { >>> + record_beneath_to_xfer_partial = t->to_xfer_partial; >>> + record_beneath_to_xfer_partial_ops = t; >>> + } >>> + if (!record_beneath_to_insert_breakpoint) >>> + record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; >>> + if (!record_beneath_to_remove_breakpoint) >>> + record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; >>> + } >>> + if (!record_beneath_to_resume) >>> + error (_("Process record can't get to_resume.")); >>> + if (!record_beneath_to_wait) >>> + error (_("Process record can't get to_wait.")); >>> + if (!record_beneath_to_store_registers) >>> + error (_("Process record can't get to_store_registers.")); >>> + if (!record_beneath_to_xfer_partial) >>> + error (_("Process record can't get to_xfer_partial.")); >>> + if (!record_beneath_to_insert_breakpoint) >>> + error (_("Process record can't get to_insert_breakpoint.")); >>> + if (!record_beneath_to_remove_breakpoint) >>> + error (_("Process record can't get to_remove_breakpoint.")); >> >> As I said above, this is better than making target.c aware of >> these pointers, but, it still isn't ideal. For one thing, if >> some other layer/target is pushed after the record target is opened, >> these will be wrong. I would prefer that this beneath lookup >> would be done at each callback implementation (record_resume, etc.), >> but, I'm happy enough with this for now. It is now contained, so we can >> clean this up afterwards... >> >>> + >>> + push_target (&record_ops); >>> + >>> + /* Reset */ >>> + record_insn_num = 0; >>> + record_list = &record_first; >>> + record_list->next = NULL; >>> +} >>> + >>> +static void >>> +record_close (int quitting) >>> +{ >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); >>> + >>> + record_list_release (record_list); >>> +} >> >> Shouldn't this clear the record_beneath_* pointers as well? >> >>> + >>> +static void >>> +record_resume (struct target_ops *ops, ptid_t ptid, int step, >>> + enum target_signal siggnal) >>> +{ >>> + record_resume_step = step; >>> + record_resume_siggnal = siggnal; >>> + >>> + if (!RECORD_IS_REPLAY) >>> + { >>> + record_message (current_gdbarch); >>> + record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, >>> + siggnal); >>> + } >>> +} >>> + >>> +static void >>> +record_sig_handler (int signo) >>> +{ >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); >>> + >>> + record_resume_step = 1; >>> + record_get_sig = 1; >>> +} >> >> This handler is magical. Why is it setting resume_step, for instance? >> It would definitelly benefic from some comments. In fact, most of the >> file is undercommented. >> >>> + >>> +static void >>> +record_wait_cleanups (void *ignore) >>> +{ >>> + if (execution_direction == EXEC_REVERSE) >>> + { >>> + if (record_list->next) >>> + record_list = record_list->next; >>> + } >>> + else >>> + record_list = record_list->prev; >>> + >>> + set_executing (inferior_ptid, 0); >>> + record_normal_stop (); >>> +} >> >> See comments about record_normal_stop above. >> >>> + >>> +/* record_wait >> >> Please remove the function name from the comment. It's redundant. >> >>> + In replay mode, this function examines the recorded log and >>> + determines where to stop. */ >>> + >>> +static ptid_t >>> +record_wait (struct target_ops *ops, >>> + ptid_t ptid, struct target_waitstatus *status) >>> +{ >>> + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); >>> + >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: record_wait " >>> + "record_resume_step = %d\n", >>> + record_resume_step); >>> + >>> + if (!RECORD_IS_REPLAY) >>> + { >>> + if (record_resume_step) >>> + { >>> + /* This is a single step. */ >>> + return record_beneath_to_wait (record_beneath_to_wait_ops, >>> + ptid, status); >>> + } >>> + else >>> + { >>> + if (record_resume_step) >>> + { >>> + /* This is a single step. */ >>> + return record_beneath_to_wait (record_beneath_to_wait_ops, >>> + ptid, status); >>> + } >>> + else >>> + { >>> + /* This is not a single step. */ >>> + ptid_t ret; >>> + int is_breakpoint = 1; >>> + CORE_ADDR pc = 0; >>> + int pc_is_read = 0; >>> + struct bp_location *bl; >>> + struct breakpoint *b; >>> + >>> + do >>> + { >>> + ret = record_beneath_to_wait (record_beneath_to_wait_ops, >>> + ptid, status); >>> + >>> + if (status->kind == TARGET_WAITKIND_STOPPED >>> + && status->value.sig == TARGET_SIGNAL_TRAP) >>> + { >>> + /* Check if there is a breakpoint. */ >>> + pc_is_read = 0; >>> + registers_changed (); >>> + for (bl = bp_location_chain; bl; bl = bl->global_next) >> >> This will need to be fixed. Can you use the breakpoint_here-like functions >> exported by breakpoint.h instead of referencing bp_location_chain directly? >> >>> + { >>> + b = bl->owner; >>> + gdb_assert (b); >>> + if (b->enable_state != bp_enabled >>> + && b->enable_state != bp_permanent) >>> + continue; >>> + if (!pc_is_read) >>> + { >>> + pc = >>> + regcache_read_pc (get_thread_regcache (ret)); >>> + pc_is_read = 1; >>> + } >>> + switch (b->type) >>> + { >>> + default: >>> + if (bl->address == pc) >>> + goto breakpoint; >>> + break; >>> + >>> + case bp_watchpoint: >>> + /* XXX teawater: I still not very clear how to >>> + deal with it. */ >>> + goto breakpoint; >>> + break; >>> + >>> + case bp_catchpoint: >>> + gdb_assert (b->ops != NULL >>> + && b->ops->breakpoint_hit != NULL); >>> + if (b->ops->breakpoint_hit (b)) >>> + goto breakpoint; >>> + break; >>> + >>> + case bp_hardware_watchpoint: >>> + case bp_read_watchpoint: >>> + case bp_access_watchpoint: >>> + if (STOPPED_BY_WATCHPOINT (0)) >>> + goto breakpoint; >>> + break; >>> + } >>> + } >>> + >>> + /* There is not a breakpoint. */ >>> + record_message (current_gdbarch); >>> + record_beneath_to_resume (record_beneath_to_resume_ops, >>> + ptid, 1, >>> + record_resume_siggnal); >>> + continue; >>> + } >>> + >>> + is_breakpoint = 0; >>> + >>> + breakpoint: >>> + /* Add gdbarch_decr_pc_after_break to pc because gdb will >>> + expect the pc to be at address plus decr_pc_after_break >>> + when the inferior stops at a breakpoint. */ >>> + if (is_breakpoint) >>> + { >>> + CORE_ADDR decr_pc_after_break = >>> + gdbarch_decr_pc_after_break (current_gdbarch); >>> + if (decr_pc_after_break) >>> + { >>> + if (!pc_is_read) >>> + pc = >>> + regcache_read_pc (get_thread_regcache (ret)); >>> + regcache_write_pc (get_thread_regcache (ret), >>> + pc + decr_pc_after_break); >>> + } >>> + } >>> + >>> + break; >>> + } >>> + while (1); >>> + >>> + return ret; >>> + } >>> + } >>> + } >>> + else >>> + { >>> + int need_dasm = 0; >>> + struct regcache *regcache = get_current_regcache (); >>> + int continue_flag = 1; >>> + int first_record_end = 1; >>> + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); >>> + CORE_ADDR tmp_pc; >>> + >>> + status->kind = TARGET_WAITKIND_STOPPED; >>> + >>> + /* Check breakpoint when forward execute. */ >>> + if (execution_direction == EXEC_FORWARD) >>> + { >>> + tmp_pc = regcache_read_pc (regcache); >>> + if (breakpoint_inserted_here_p (tmp_pc)) >>> + { >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: break at 0x%s.\n", >>> + paddr_nz (tmp_pc)); >>> + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) >>> + && !record_resume_step) >>> + regcache_write_pc (regcache, >>> + tmp_pc + >>> + gdbarch_decr_pc_after_break >>> + (get_regcache_arch (regcache))); >>> + goto replay_out; >>> + } >>> + } >>> + >>> + record_get_sig = 0; >>> + signal (SIGINT, record_sig_handler); >>> + /* If GDB is in terminal_inferior mode, it will not get the signal. >>> + And in GDB replay mode, GDB doesn't need to be in terminal_inferior >>> + mode, because inferior will not executed. >>> + Then set it to terminal_ours to make GDB get the signal. */ >>> + target_terminal_ours (); >>> + >>> + /* In EXEC_FORWARD mode, record_list points to the tail of prev >>> + instruction. */ >>> + if (execution_direction == EXEC_FORWARD && record_list->next) >>> + record_list = record_list->next; >>> + >>> + /* Loop over the record_list, looking for the next place to >>> + stop. */ >>> + do >>> + { >>> + /* Check for beginning and end of log. */ >>> + if (execution_direction == EXEC_REVERSE >>> + && record_list == &record_first) >>> + { >>> + /* Hit beginning of record log in reverse. */ >>> + status->kind = TARGET_WAITKIND_NO_HISTORY; >>> + break; >>> + } >>> + if (execution_direction != EXEC_REVERSE && !record_list->next) >>> + { >>> + /* Hit end of record log going forward. */ >>> + status->kind = TARGET_WAITKIND_NO_HISTORY; >>> + break; >>> + } >>> + >>> + /* Set ptid, register and memory according to record_list. */ >>> + if (record_list->type == record_reg) >>> + { >>> + /* reg */ >>> + gdb_byte reg[MAX_REGISTER_SIZE]; >>> + if (record_debug > 1) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: record_reg %s to " >>> + "inferior num = %d.\n", >>> + host_address_to_string (record_list), >>> + record_list->u.reg.num); >>> + regcache_cooked_read (regcache, record_list->u.reg.num, reg); >>> + regcache_cooked_write (regcache, record_list->u.reg.num, >>> + record_list->u.reg.val); >>> + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); >>> + } >>> + else if (record_list->type == record_mem) >>> + { >>> + /* mem */ >>> + gdb_byte *mem = alloca (record_list->u.mem.len); >>> + if (record_debug > 1) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: record_mem %s to " >>> + "inferior addr = 0x%s len = %d.\n", >>> + host_address_to_string (record_list), >>> + paddr_nz (record_list->u.mem.addr), >>> + record_list->u.mem.len); >>> + >>> + if (target_read_memory >>> + (record_list->u.mem.addr, mem, record_list->u.mem.len)) >>> + error (_("Process record: error reading memory at " >>> + "addr = 0x%s len = %d."), >>> + paddr_nz (record_list->u.mem.addr), >>> + record_list->u.mem.len); >>> + >>> + if (target_write_memory >>> + (record_list->u.mem.addr, record_list->u.mem.val, >>> + record_list->u.mem.len)) >>> + error (_ >>> + ("Process record: error writing memory at " >>> + "addr = 0x%s len = %d."), >>> + paddr_nz (record_list->u.mem.addr), >>> + record_list->u.mem.len); >>> + >>> + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); >>> + } >>> + else >>> + { >>> + if (record_debug > 1) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: record_end %s to " >>> + "inferior need_dasm = %d.\n", >>> + host_address_to_string (record_list), >>> + record_list->u.need_dasm); >>> + >>> + if (execution_direction == EXEC_FORWARD) >>> + need_dasm = record_list->u.need_dasm; >>> + if (need_dasm) >>> + gdbarch_process_record_dasm (current_gdbarch); >>> + >>> + if (first_record_end && execution_direction == EXEC_REVERSE) >>> + { >>> + /* When reverse excute, the first record_end is the part of >>> + current instruction. */ >>> + first_record_end = 0; >>> + } >>> + else >>> + { >>> + /* In EXEC_REVERSE mode, this is the record_end of prev >>> + instruction. >>> + In EXEC_FORWARD mode, this is the record_end of current >>> + instruction. */ >>> + /* step */ >>> + if (record_resume_step) >>> + { >>> + if (record_debug > 1) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: step.\n"); >>> + continue_flag = 0; >>> + } >>> + >>> + /* check breakpoint */ >>> + tmp_pc = regcache_read_pc (regcache); >>> + if (breakpoint_inserted_here_p (tmp_pc)) >>> + { >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, >>> + "Process record: break " >>> + "at 0x%s.\n", >>> + paddr_nz (tmp_pc)); >>> + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) >>> + && execution_direction == EXEC_FORWARD >>> + && !record_resume_step) >>> + regcache_write_pc (regcache, >>> + tmp_pc + >>> + gdbarch_decr_pc_after_break >>> + (get_regcache_arch (regcache))); >>> + continue_flag = 0; >>> + } >>> + } >>> + if (execution_direction == EXEC_REVERSE) >>> + need_dasm = record_list->u.need_dasm; >>> + } >>> + >>> +next: >>> + if (continue_flag) >>> + { >>> + if (execution_direction == EXEC_REVERSE) >>> + { >>> + if (record_list->prev) >>> + record_list = record_list->prev; >>> + } >>> + else >>> + { >>> + if (record_list->next) >>> + record_list = record_list->next; >>> + } >>> + } >>> + } >>> + while (continue_flag); >>> + >>> + signal (SIGINT, handle_sigint); >>> + >>> +replay_out: >>> + if (record_get_sig) >>> + status->value.sig = TARGET_SIGNAL_INT; >>> + else >>> + status->value.sig = TARGET_SIGNAL_TRAP; >>> + >>> + discard_cleanups (old_cleanups); >>> + } >>> + >>> + do_cleanups (set_cleanups); >>> + return inferior_ptid; >>> +} >> >> I have to say that I find that function confusing, due to >> the use of gotos, and excessive nesting. Personally, I much prefer >> code that does: >> >> if (foo) >> { >> /* something */ >> return; >> } >> >> if (bar) >> { >> /* something */ >> return; >> } >> >> if (lala) >> { >> /* something */ >> return; >> } >> >> Over: >> >> if (foo) >> { >> /* something */ >> return; >> } >> else >> { >> if (bar) >> { >> /* something */ >> return; >> } >> else >> { >> if (lala) >> { >> /* something */ >> return; >> } >> } >> } >> >> >>> + >>> +static void >>> +record_disconnect (struct target_ops *target, char *args, int from_tty) >>> +{ >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); >>> + >>> + unpush_target (&record_ops); >>> + target_disconnect (args, from_tty); >>> +} >>> + >>> +static void >>> +record_detach (struct target_ops *ops, char *args, int from_tty) >>> +{ >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); >>> + >>> + unpush_target (&record_ops); >>> + target_detach (args, from_tty); >>> +} >> >> This trick you're using happens to work, but, could you try >> this instead? Here and elsewhere similarly. >> >> struct target_ops *beneath = find_target_beaneath (ops); >> unpush_target (ops); >> beneath->to_detach (args, from_tty); >> >>> + >>> +static void >>> +record_mourn_inferior (struct target_ops *ops) >>> +{ >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, "Process record: " >>> + "record_mourn_inferior\n"); >>> + >>> + unpush_target (&record_ops); >>> + target_mourn_inferior (); >>> +} >>> + >>> +/* Close process record target before killing the inferior process. */ >>> +static void >>> +record_kill (void) >>> +{ >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); >>> + >>> + unpush_target (&record_ops); >>> + target_kill (); >>> +} >>> + >>> +/* Record registers change (by user or by GDB) to list as an instruction. */ >>> +static void >>> +record_registers_change (struct regcache *regcache, int regnum) >>> +{ >>> + /* Check record_insn_num. */ >>> + record_check_insn_num (0); >>> + >>> + record_arch_list_head = NULL; >>> + record_arch_list_tail = NULL; >>> + >>> + record_regcache = get_current_regcache (); >>> + >>> + if (regnum < 0) >>> + { >>> + int i; >>> + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) >>> + { >>> + if (record_arch_list_add_reg (i)) >>> + { >>> + record_list_release (record_arch_list_tail); >>> + error (_("Process record: failed to record execution log.")); >>> + } >>> + } >>> + } >>> + else >>> + { >>> + if (record_arch_list_add_reg (regnum)) >>> + { >>> + record_list_release (record_arch_list_tail); >>> + error (_("Process record: failed to record execution log.")); >>> + } >>> + } >>> + if (record_arch_list_add_end (0)) >>> + { >>> + record_list_release (record_arch_list_tail); >>> + error (_("Process record: failed to record execution log.")); >>> + } >>> + record_list->next = record_arch_list_head; >>> + record_arch_list_head->prev = record_list; >>> + record_list = record_arch_list_tail; >>> + >>> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >>> + record_list_release_first (); >>> + else >>> + record_insn_num++; >>> +} >>> + >>> +static void >>> +record_store_registers (struct target_ops *ops, struct regcache *regcache, >>> + int regno) >>> +{ >>> + if (!record_gdb_operation_disable) >>> + { >>> + if (RECORD_IS_REPLAY) >>> + { >>> + int n; >>> + struct cleanup *old_cleanups; >>> + >>> + /* Let user choose if he wants to write register or not. */ >>> + if (regno < 0) >>> + n = >>> + nquery (_("Because GDB is in replay mode, changing the " >>> + "value of a register will make the execution " >>> + "log unusable from this point onward. " >>> + "Change all registers?")); >>> + else >>> + n = >>> + nquery (_("Because GDB is in replay mode, changing the value " >>> + "of a register will make the execution log unusable " >>> + "from this point onward. Change register %s?"), >>> + gdbarch_register_name (get_regcache_arch (regcache), >>> + regno)); >>> + >>> + if (!n) >>> + { >>> + /* Invalidate the value of regcache that was set in function >>> + "regcache_raw_write". */ >>> + if (regno < 0) >>> + { >>> + int i; >>> + for (i = 0; >>> + i < gdbarch_num_regs (get_regcache_arch (regcache)); >>> + i++) >>> + regcache_invalidate (regcache, i); >>> + } >>> + else >>> + regcache_invalidate (regcache, regno); >>> + >>> + error (_("Process record canceled the operation.")); >>> + } >>> + >>> + /* Destroy the record from here forward. */ >>> + record_list_release_next (); >>> + } >>> + >>> + record_registers_change (regcache, regno); >>> + } >>> + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, >>> + regcache, regno); >>> +} >>> + >>> +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. >>> + In replay mode, we cannot write memory unles we are willing to >>> + invalidate the record/replay log from this point forward. */ >>> + >>> +static LONGEST >>> +record_xfer_partial (struct target_ops *ops, enum target_object object, >>> + const char *annex, gdb_byte * readbuf, >>> + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) >>> +{ >>> + if (!record_gdb_operation_disable >>> + && (object == TARGET_OBJECT_MEMORY >>> + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) >>> + { >>> + if (RECORD_IS_REPLAY) >>> + { >>> + /* Let user choose if he wants to write memory or not. */ >>> + if (!nquery (_("Because GDB is in replay mode, writing to memory " >>> + "will make the execution log unusable from this " >>> + "point onward. Write memory at address 0x%s?"), >>> + paddr_nz (offset))) >>> + return -1; >>> + >>> + /* Destroy the record from here forward. */ >>> + record_list_release_next (); >>> + } >>> + >>> + /* Check record_insn_num */ >>> + record_check_insn_num (0); >>> + >>> + /* Record registers change to list as an instruction. */ >>> + record_arch_list_head = NULL; >>> + record_arch_list_tail = NULL; >>> + if (record_arch_list_add_mem (offset, len)) >>> + { >>> + record_list_release (record_arch_list_tail); >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, >>> + _("Process record: failed to record " >>> + "execution log.")); >>> + return -1; >>> + } >>> + if (record_arch_list_add_end (0)) >>> + { >>> + record_list_release (record_arch_list_tail); >>> + if (record_debug) >>> + fprintf_unfiltered (gdb_stdlog, >>> + _("Process record: failed to record " >>> + "execution log.")); >>> + return -1; >>> + } >>> + record_list->next = record_arch_list_head; >>> + record_arch_list_head->prev = record_list; >>> + record_list = record_arch_list_tail; >>> + >>> + if (record_insn_num == record_insn_max_num && record_insn_max_num) >>> + record_list_release_first (); >>> + else >>> + record_insn_num++; >>> + } >>> + >>> + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, >>> + object, annex, readbuf, writebuf, >>> + offset, len); >>> +} >>> + >>> +/* record_insert_breakpoint >>> + record_remove_breakpoint >>> + Behavior is conditional on RECORD_IS_REPLAY. >>> + We will not actually insert or remove breakpoints when replaying, >>> + nor when recording. */ >>> + >>> +static int >>> +record_insert_breakpoint (struct bp_target_info *bp_tgt) >>> +{ >>> + if (!RECORD_IS_REPLAY) >>> + { >>> + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); >>> + int ret = record_beneath_to_insert_breakpoint (bp_tgt); >>> + >>> + do_cleanups (old_cleanups); >>> + >>> + return ret; >>> + } >>> + >>> + return 0; >>> +} >>> + >>> +static int >>> +record_remove_breakpoint (struct bp_target_info *bp_tgt) >>> +{ >>> + if (!RECORD_IS_REPLAY) >>> + { >>> + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); >>> + int ret = record_beneath_to_remove_breakpoint (bp_tgt); >>> + >>> + do_cleanups (old_cleanups); >>> + >>> + return ret; >>> + } >>> + >>> + return 0; >>> +} >>> + >>> +static int >>> +record_can_execute_reverse (void) >>> +{ >>> + return 1; >>> +} >>> + >>> +static void >>> +init_record_ops (void) >>> +{ >>> + record_ops.to_shortname = "record"; >>> + record_ops.to_longname = "Process record and replay target"; >>> + record_ops.to_doc = >>> + "Log program while executing and replay execution from log."; >>> + record_ops.to_open = record_open; >>> + record_ops.to_close = record_close; >>> + record_ops.to_resume = record_resume; >>> + record_ops.to_wait = record_wait; >>> + record_ops.to_disconnect = record_disconnect; >>> + record_ops.to_detach = record_detach; >>> + record_ops.to_mourn_inferior = record_mourn_inferior; >>> + record_ops.to_kill = record_kill; >>> + record_ops.to_create_inferior = find_default_create_inferior; >>> + record_ops.to_store_registers = record_store_registers; >>> + record_ops.to_xfer_partial = record_xfer_partial; >>> + record_ops.to_insert_breakpoint = record_insert_breakpoint; >>> + record_ops.to_remove_breakpoint = record_remove_breakpoint; >>> + record_ops.to_can_execute_reverse = record_can_execute_reverse; >>> + record_ops.to_stratum = record_stratum; >>> + record_ops.to_magic = OPS_MAGIC; >>> +} >>> + >>> +static void >>> +show_record_debug (struct ui_file *file, int from_tty, >>> + struct cmd_list_element *c, const char *value) >>> +{ >>> + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), >>> + value); >>> +} >>> + >>> +/* cmd_record_start -- alias for "target record". */ >>> + >>> +static void >>> +cmd_record_start (char *args, int from_tty) >>> +{ >>> + execute_command ("target record", from_tty); >>> +} >>> + >>> +/* cmd_record_delete -- truncate the record log from the present point >>> + of replay until the end. */ >>> + >>> +static void >>> +cmd_record_delete (char *args, int from_tty) >>> +{ >>> + if (TARGET_IS_PROCESS_RECORD) >>> + { >>> + if (RECORD_IS_REPLAY) >>> + { >>> + if (!from_tty || query (_("Delete the log from this point forward " >>> + "and begin to record the running message " >>> + "at current PC?"))) >>> + record_list_release_next (); >>> + } >>> + else >>> + printf_unfiltered (_("Already at end of record list.\n")); >>> + >>> + } >>> + else >>> + printf_unfiltered (_("Process record is not started.\n")); >>> +} >>> + >>> +/* cmd_record_stop -- implement the "stoprecord" command. */ >>> + >>> +static void >>> +cmd_record_stop (char *args, int from_tty) >>> +{ >>> + if (TARGET_IS_PROCESS_RECORD) >>> + { >>> + if (!record_list || !from_tty || query (_("Delete recorded log and " >>> + "stop recording?"))) >>> + unpush_target (&record_ops); >>> + } >>> + else >>> + printf_unfiltered (_("Process record is not started.\n")); >>> +} >>> + >>> +/* set_record_insn_max_num -- set upper limit of record log size. */ >>> + >>> +static void >>> +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) >>> +{ >>> + if (record_insn_num > record_insn_max_num && record_insn_max_num) >>> + { >>> + printf_unfiltered (_("Record instructions number is bigger than " >>> + "record instructions max number. Auto delete " >>> + "the first ones?\n")); >>> + >>> + while (record_insn_num > record_insn_max_num) >>> + record_list_release_first (); >>> + } >>> +} >>> + >>> +/* show_record_insn_number -- print the current index >>> + into the record log (number of insns recorded so far). */ >>> + >>> +static void >>> +show_record_insn_number (char *ignore, int from_tty) >>> +{ >>> + printf_unfiltered (_("Record instruction number is %d.\n"), >>> + record_insn_num); >>> +} >>> + >>> +void >>> +_initialize_record (void) >>> +{ >>> + /* Init record_maskall. */ >>> + if (sigfillset (&record_maskall) == -1) >>> + perror_with_name (_("Process record: sigfillset failed")); >> >> This will not build on all hosts. Is it still needed? I can't >> find any other reference to this variable in this patch. >> >>> + >>> + /* Init record_first. */ >>> + record_first.prev = NULL; >>> + record_first.next = NULL; >>> + record_first.type = record_end; >>> + record_first.u.need_dasm = 0; >>> + >>> + init_record_ops (); >>> + add_target (&record_ops); >>> + >>> + add_setshow_zinteger_cmd ("record", no_class, &record_debug, >>> + _("Set debugging of record/replay feature."), >>> + _("Show debugging of record/replay feature."), >>> + _("When enabled, debugging output for " >>> + "record/replay feature is displayed."), >>> + NULL, show_record_debug, &setdebuglist, >>> + &showdebuglist); >>> + >>> + add_com ("record", class_obscure, cmd_record_start, >>> + _("Abbreviated form of \"target record\" command.")); >>> + >>> + add_com_alias ("rec", "record", class_obscure, 1); >>> + >>> + /* XXX: I try to use some simple commands such as "disconnect" and >>> + "detach" to support this functions. But these commands all have >>> + other affect to GDB such as call function "no_shared_libraries". >>> + So I add special commands to GDB. */ >>> + add_com ("delrecord", class_obscure, cmd_record_delete, >>> + _("Delete the rest of execution log and start recording it anew.")); >>> + add_com_alias ("dr", "delrecord", class_obscure, 1); >>> + add_com ("stoprecord", class_obscure, cmd_record_stop, >>> + _("Stop the record/replay target.")); >>> + add_com_alias ("sr", "stoprecord", class_obscure, 1); >>> + >>> + /* Record instructions number limit command. */ >>> + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, >>> + &record_stop_at_limit, >>> + _("Set whether record/replay stop when " >>> + "record/replay buffer becomes full."), >>> + _("Show whether record/replay stop when " >>> + "record/replay buffer becomes full."), >>> + _("Enable is default value.\n" >>> + "When enabled, if the record/replay buffer " >>> + "becomes full,\n" >>> + "ask user what to do.\n" >>> + "When disabled, if the record/replay buffer " >>> + "becomes full,\n" >>> + "delete it and start new recording."), >>> + NULL, NULL, &setlist, &showlist); >>> + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, >>> + &record_insn_max_num, >>> + _("Set record/replay buffer limit."), >>> + _("Show record/replay buffer limit."), >>> + _("Set the maximum number of instructions to be " >>> + "stored in the\n" >>> + "record/replay buffer. " >>> + "Zero means unlimited (default 200000)."), >>> + set_record_insn_max_num, >>> + NULL, &setlist, &showlist); >>> + add_info ("record-insn-number", show_record_insn_number, >>> + _("Show the current number of instructions in the " >>> + "record/replay buffer.")); >>> +} >>> Index: src/gdb/record.h >>> =================================================================== >>> --- /dev/null 1970-01-01 00:00:00.000000000 +0000 >>> +++ src/gdb/record.h 2009-02-28 20:23:20.000000000 +0000 >>> @@ -0,0 +1,87 @@ >>> +/* Process record and replay target for GDB, the GNU debugger. >>> + >>> + Copyright (C) 2008 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/>. */ >>> + >>> +#ifndef _RECORD_H_ >>> +#define _RECORD_H_ >>> + >>> +#define TARGET_IS_PROCESS_RECORD \ >>> + (current_target.beneath == &record_ops) >> >> Sorry, but I repeat the request I've made several times already. This is >> not the right way to do this. You need to add a new target_ops method or >> property that the core of GDB checks on. It is not correct that make >> the core of GDB reference record_ops directly. To come up with >> the target callback's name, at each call site of TARGET_IS_PROCESS_RECORD, >> consider what is the property of the current target that GDB needs to >> know about the current target. Is it something like: >> >> target_is_recording () ? >> target_is_replaying () ? >> target_is_read_only () ? >> >> etc. >> >>> +#define RECORD_IS_REPLAY \ >>> + (record_list->next || execution_direction == EXEC_REVERSE) >> >> AFAICS, this macro is not used outside of record.c. It should move >> there, along with anything that isn't used outside of record.c. >> >>> + >>> +typedef struct record_reg_s >>> +{ >>> + int num; >>> + gdb_byte *val; >>> +} record_reg_t; >>> + >>> +typedef struct record_mem_s >>> +{ >>> + CORE_ADDR addr; >>> + int len; >>> + gdb_byte *val; >>> +} record_mem_t; >>> + >>> +enum record_type >>> +{ >>> + record_end = 0, >>> + record_reg, >>> + record_mem >>> +}; >>> + >>> +/* This is the core struct of record function. >>> + >>> + An entity of record_t is a record of the value change of a register >>> + ("record_reg") or a part of memory ("record_mem"). And each >>> + instruction must has a record_t ("record_end") that points out this >>> + is the last record_t of this instruction. >>> + >>> + Each record_t is linked to "record_list" by "prev" and "next". >>> + */ >>> +typedef struct record_s >>> +{ >>> + struct record_s *prev; >>> + struct record_s *next; >>> + enum record_type type; >>> + union >>> + { >>> + /* reg */ >>> + record_reg_t reg; >>> + /* mem */ >>> + record_mem_t mem; >>> + /* end */ >>> + int need_dasm; >>> + } u; >>> +} record_t; >>> + >>> +extern int record_debug; >>> +extern record_t *record_list; >>> +extern record_t *record_arch_list_head; >>> +extern record_t *record_arch_list_tail; >>> +extern struct regcache *record_regcache; >> >> Most of these things don't appear to be used anywhere else other >> than in record.c. Can you remove these declarations from the >> public header, and make them static in record.c? >> >>> + >>> +extern struct target_ops record_ops; >> >> Once you get rid of TARGET_IS_PROCESS_RECORD this doesn't >> need to be public anymore. >> >>> + >>> +extern int record_arch_list_add_reg (int num); >>> +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); >>> +extern int record_arch_list_add_end (int need_dasm); >>> +extern void record_message (struct gdbarch *gdbarch); >>> +extern struct cleanup * record_gdb_operation_disable_set (void); >>> + >>> +#endif /* _RECORD_H_ */ >>> >> >> -- >> Pedro Alves >> > [-- Attachment #2: prec3.tar.bz2 --] [-- Type: application/x-bzip2, Size: 24413 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-02-17 7:12 ` teawater 2009-02-17 7:21 ` teawater @ 2009-02-23 14:08 ` teawater 2009-02-28 10:02 ` teawater 1 sibling, 1 reply; 38+ messages in thread From: teawater @ 2009-02-23 14:08 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 585 bytes --] Hi Pedro, 3-record_target.txt patch update follow your "Modernize solaris threads support" patch. Thanks, Hui On Tue, Feb 17, 2009 at 15:01, teawater <teawater@gmail.com> wrote: > Update follow the cvs-head. It just has a very small change - add a 0 > to be the argument of observer_notify_normal_stop. > > On Sun, Feb 8, 2009 at 21:03, teawater <teawater@gmail.com> wrote: >> Hi Pedro, >> >> According to your mail "Get rid of linux-thread-db.c:target_beneath". >> I get ride of this global beneath function pointers. >> >> Do you think this patch is OK? >> >> Thanks, >> Hui >> > [-- Attachment #2: 3-record_target.txt --] [-- Type: text/plain, Size: 40076 bytes --] --- Makefile.in | 4 record.c | 1283 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ record.h | 87 ++++ 3 files changed, 1372 insertions(+), 2 deletions(-) --- a/Makefile.in +++ b/Makefile.in @@ -662,7 +662,7 @@ SFILES = ada-exp.y ada-lang.c ada-typepr valarith.c valops.c valprint.c value.c varobj.c vec.c \ wrapper.c \ xml-tdesc.c xml-support.c \ - inferior.c + inferior.c record.c LINTFILES = $(SFILES) $(YYFILES) $(CONFIG_SRCS) init.c @@ -813,7 +813,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $ solib.o solib-null.o \ prologue-value.o memory-map.o xml-support.o \ target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \ - inferior.o osdata.o + inferior.o osdata.o record.o TSOBS = inflow.o --- /dev/null +++ b/record.c @@ -0,0 +1,1283 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "target.h" +#include "gdbcmd.h" +#include "regcache.h" +#include "inferior.h" +#include "gdbthread.h" +#include "event-top.h" +#include "annotate.h" +#include "observer.h" +#include "record.h" + +#include <signal.h> + +#define DEFAULT_RECORD_INSN_MAX_NUM 200000 + +int record_debug = 0; + +record_t record_first; +record_t *record_list = &record_first; +record_t *record_arch_list_head = NULL; +record_t *record_arch_list_tail = NULL; +struct regcache *record_regcache = NULL; + +/* 1 ask user. 0 auto delete the last record_t. */ +static int record_stop_at_limit = 1; +static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; +static int record_insn_num = 0; + +struct target_ops record_ops; +static int record_resume_step = 0; +static enum target_signal record_resume_siggnal; +static int record_get_sig = 0; +static sigset_t record_maskall; +static int record_gdb_operation_disable = 0; +int record_will_store_registers = 0; + +extern struct bp_location *bp_location_chain; + +/* The beneath function pointers. */ +static struct target_ops *record_beneath_to_resume_ops = NULL; +static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal) = NULL; +static struct target_ops *record_beneath_to_wait_ops = NULL; +static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *) = NULL; +static struct target_ops *record_beneath_to_store_registers_ops = NULL; +static void (*record_beneath_to_store_registers) (struct target_ops *, + struct regcache *, + int regno) = NULL; +static struct target_ops *record_beneath_to_xfer_partial_ops = NULL; +static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops * ops, + enum target_object object, + const char *annex, + gdb_byte * readbuf, + const gdb_byte * writebuf, + ULONGEST offset, + LONGEST len) = NULL; +static int (*record_beneath_to_insert_breakpoint) (struct bp_target_info *) = + NULL; +static int (*record_beneath_to_remove_breakpoint) (struct bp_target_info *) = + NULL; + +static void +record_list_release (record_t * rec) +{ + record_t *tmp; + + if (!rec) + return; + + while (rec->next) + { + rec = rec->next; + } + + while (rec->prev) + { + tmp = rec; + rec = rec->prev; + if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + } + + if (rec != &record_first) + xfree (rec); +} + +static void +record_list_release_next (void) +{ + record_t *rec = record_list; + record_t *tmp = rec->next; + rec->next = NULL; + while (tmp) + { + rec = tmp->next; + if (tmp->type == record_reg) + record_insn_num--; + else if (tmp->type == record_reg) + xfree (tmp->u.reg.val); + else if (tmp->type == record_mem) + xfree (tmp->u.mem.val); + xfree (tmp); + tmp = rec; + } +} + +static void +record_list_release_first (void) +{ + record_t *tmp = NULL; + enum record_type type; + + if (!record_first.next) + return; + + while (1) + { + type = record_first.next->type; + + if (type == record_reg) + xfree (record_first.next->u.reg.val); + else if (type == record_mem) + xfree (record_first.next->u.mem.val); + tmp = record_first.next; + record_first.next = tmp->next; + xfree (tmp); + + if (!record_first.next) + { + gdb_assert (record_insn_num == 1); + break; + } + + record_first.next->prev = &record_first; + + if (type == record_end) + break; + } + + record_insn_num--; +} + +/* Add a record_t to record_arch_list. */ +static void +record_arch_list_add (record_t * rec) +{ + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_arch_list_add %s.\n", + host_address_to_string (rec)); + + if (record_arch_list_tail) + { + record_arch_list_tail->next = rec; + rec->prev = record_arch_list_tail; + record_arch_list_tail = rec; + } + else + { + record_arch_list_head = rec; + record_arch_list_tail = rec; + } +} + +/* Record the value of a register ("num") to record_arch_list. */ +int +record_arch_list_add_reg (int num) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add register num = %d to " + "record list.\n", + num); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + rec->u.reg.num = num; + + regcache_raw_read (record_regcache, num, rec->u.reg.val); + + record_arch_list_add (rec); + + return 0; +} + +/* Record the value of a region of memory whose address is "addr" and + length is "len" to record_arch_list. */ + +int +record_arch_list_add_mem (CORE_ADDR addr, int len) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add mem addr = 0x%s len = %d to " + "record list.\n", + paddr_nz (addr), len); + + if (!addr) + return 0; + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->u.mem.val = (gdb_byte *) xmalloc (len); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + rec->u.mem.addr = addr; + rec->u.mem.len = len; + + if (target_read_memory (addr, rec->u.mem.val, len)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: error reading memory at " + "addr = 0x%s len = %d.\n", + paddr_nz (addr), len); + xfree (rec->u.mem.val); + xfree (rec); + return -1; + } + + record_arch_list_add (rec); + + return 0; +} + +/* Add a record_end type record_t to record_arch_list. */ +int +record_arch_list_add_end (int need_dasm) +{ + record_t *rec; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: add end need_dasm = %d to " + "arch list.\n", + need_dasm); + + rec = (record_t *) xmalloc (sizeof (record_t)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + + rec->u.need_dasm = need_dasm; + + record_arch_list_add (rec); + + return 0; +} + +static void +record_check_insn_num (int set_terminal) +{ + if (record_insn_max_num) + { + gdb_assert (record_insn_num <= record_insn_max_num); + if (record_insn_num == record_insn_max_num) + { + /* Ask user what to do. */ + if (record_stop_at_limit) + { + int q; + if (set_terminal) + target_terminal_ours (); + q = yquery (_("Do you want to auto delete previous execution " + "log entries when record/replay buffer becomes " + "full (record-stop-at-limit)?")); + if (set_terminal) + target_terminal_inferior (); + if (q) + record_stop_at_limit = 0; + else + error (_("Process record: inferior program stopped.")); + } + } + } +} + +static void +record_normal_stop (void) +{ + finish_thread_state (minus_one_ptid); + + if (!breakpoints_always_inserted_mode () && target_has_execution) + remove_breakpoints (); + + target_terminal_ours (); + + if (target_has_stack && !stop_stack_dummy) + set_current_sal_from_frame (get_current_frame (), 1); + + select_frame (get_current_frame ()); + + annotate_stopped (); + if (!suppress_stop_observer) + { + if (!ptid_equal (inferior_ptid, null_ptid)) + observer_notify_normal_stop (inferior_thread ()->stop_bpstat, 0); + else + observer_notify_normal_stop (NULL, 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_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_list_release (record_arch_list_tail); + set_executing (inferior_ptid, 0); + record_normal_stop (); +} + +void +record_message (struct gdbarch *gdbarch) +{ + int ret; + struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + /* Check record_insn_num. */ + record_check_insn_num (1); + + record_regcache = get_current_regcache (); + + ret = gdbarch_process_record (gdbarch, + regcache_read_pc (record_regcache)); + if (ret > 0) + error (_("Process record: inferior program stopped.")); + if (ret < 0) + error (_("Process record: failed to record execution log.")); + + discard_cleanups (old_cleanups); + + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +/* Things to clean up if we error or QUIT out of function that set + record_gdb_operation_disable (ie. command that caused target_wait to + be called). */ +static void +record_gdb_operation_disable_cleanups (void *ignore) +{ + record_gdb_operation_disable = 0; +} + +struct cleanup * +record_gdb_operation_disable_set (void) +{ + struct cleanup *old_cleanups = + make_cleanup (record_gdb_operation_disable_cleanups, 0); + record_gdb_operation_disable = 1; + + return old_cleanups; +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + + /* check exec */ + if (!target_has_execution) + error (_("Process record: the program is not being run.")); + if (non_stop) + error (_("Process record target can't debug inferior in non-stop mode " + "(non-stop).")); + if (target_async_permitted) + error (_("Process record target can't debug inferior in asynchronous " + "mode (target-async).")); + + if (!gdbarch_process_record_p (current_gdbarch)) + error (_("Process record: the current architecture doesn't support " + "record function.")); + + /* Check if record target is already running. */ + if (TARGET_IS_PROCESS_RECORD) + { + if (!nquery + (_("Process record target already running, do you want to delete " + "the old record log?"))) + return; + } + + /* Set the beneath function pointers. */ + for (t = current_target.beneath; t != NULL; t = t->beneath) + { + if (!record_beneath_to_resume) + { + record_beneath_to_resume = t->to_resume; + record_beneath_to_resume_ops = t; + } + if (!record_beneath_to_wait) + { + record_beneath_to_wait = t->to_wait; + record_beneath_to_wait_ops = t; + } + if (!record_beneath_to_store_registers) + { + record_beneath_to_store_registers = t->to_store_registers; + record_beneath_to_store_registers_ops = t; + } + if (!record_beneath_to_xfer_partial) + { + record_beneath_to_xfer_partial = t->to_xfer_partial; + record_beneath_to_xfer_partial_ops = t; + } + if (!record_beneath_to_insert_breakpoint) + record_beneath_to_insert_breakpoint = t->to_insert_breakpoint; + if (!record_beneath_to_remove_breakpoint) + record_beneath_to_remove_breakpoint = t->to_remove_breakpoint; + } + if (!record_beneath_to_resume) + error (_("Process record can't get to_resume.")); + if (!record_beneath_to_wait) + error (_("Process record can't get to_wait.")); + if (!record_beneath_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!record_beneath_to_xfer_partial) + error (_("Process record can't get to_xfer_partial.")); + if (!record_beneath_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!record_beneath_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); + + /* Reset */ + record_insn_num = 0; + record_list = &record_first; + record_list->next = NULL; +} + +static void +record_close (int quitting) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); + + record_list_release (record_list); +} + +static void +record_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; + + if (!RECORD_IS_REPLAY) + { + record_message (current_gdbarch); + record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, + siggnal); + } +} + +static void +record_sig_handler (int signo) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); + + record_resume_step = 1; + record_get_sig = 1; +} + +static void +record_wait_cleanups (void *ignore) +{ + if (execution_direction == EXEC_REVERSE) + { + if (record_list->next) + record_list = record_list->next; + } + else + record_list = record_list->prev; + + set_executing (inferior_ptid, 0); + record_normal_stop (); +} + +/* record_wait + In replay mode, this function examines the recorded log and + determines where to stop. */ + +static ptid_t +record_wait (struct target_ops *ops, + ptid_t ptid, struct target_waitstatus *status) +{ + struct cleanup *set_cleanups = record_gdb_operation_disable_set (); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_wait " + "record_resume_step = %d\n", + record_resume_step); + + if (!RECORD_IS_REPLAY) + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (record_beneath_to_wait_ops, + ptid, status); + } + else + { + if (record_resume_step) + { + /* This is a single step. */ + return record_beneath_to_wait (record_beneath_to_wait_ops, + ptid, status); + } + else + { + /* This is not a single step. */ + ptid_t ret; + int is_breakpoint = 1; + CORE_ADDR pc = 0; + int pc_is_read = 0; + struct bp_location *bl; + struct breakpoint *b; + + do + { + ret = record_beneath_to_wait (record_beneath_to_wait_ops, + ptid, status); + + if (status->kind == TARGET_WAITKIND_STOPPED + && status->value.sig == TARGET_SIGNAL_TRAP) + { + /* Check if there is a breakpoint. */ + pc_is_read = 0; + registers_changed (); + for (bl = bp_location_chain; bl; bl = bl->global_next) + { + b = bl->owner; + gdb_assert (b); + if (b->enable_state != bp_enabled + && b->enable_state != bp_permanent) + continue; + if (!pc_is_read) + { + pc = + regcache_read_pc (get_thread_regcache (ret)); + pc_is_read = 1; + } + switch (b->type) + { + default: + if (bl->address == pc) + goto breakpoint; + break; + + case bp_watchpoint: + /* XXX teawater: I still not very clear how to + deal with it. */ + goto breakpoint; + break; + + case bp_catchpoint: + gdb_assert (b->ops != NULL + && b->ops->breakpoint_hit != NULL); + if (b->ops->breakpoint_hit (b)) + goto breakpoint; + break; + + case bp_hardware_watchpoint: + case bp_read_watchpoint: + case bp_access_watchpoint: + if (STOPPED_BY_WATCHPOINT (0)) + goto breakpoint; + break; + } + } + + /* There is not a breakpoint. */ + record_message (current_gdbarch); + record_beneath_to_resume (record_beneath_to_resume_ops, + ptid, 1, + record_resume_siggnal); + continue; + } + + is_breakpoint = 0; + + breakpoint: + /* Add gdbarch_decr_pc_after_break to pc because gdb will + expect the pc to be at address plus decr_pc_after_break + when the inferior stops at a breakpoint. */ + if (is_breakpoint) + { + CORE_ADDR decr_pc_after_break = + gdbarch_decr_pc_after_break (current_gdbarch); + if (decr_pc_after_break) + { + if (!pc_is_read) + pc = + regcache_read_pc (get_thread_regcache (ret)); + regcache_write_pc (get_thread_regcache (ret), + pc + decr_pc_after_break); + } + } + + break; + } + while (1); + + return ret; + } + } + } + else + { + int need_dasm = 0; + struct regcache *regcache = get_current_regcache (); + int continue_flag = 1; + int first_record_end = 1; + struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); + CORE_ADDR tmp_pc; + + status->kind = TARGET_WAITKIND_STOPPED; + + /* Check breakpoint when forward execute. */ + if (execution_direction == EXEC_FORWARD) + { + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + goto replay_out; + } + } + + record_get_sig = 0; + signal (SIGINT, record_sig_handler); + /* If GDB is in terminal_inferior mode, it will not get the signal. + And in GDB replay mode, GDB doesn't need to be in terminal_inferior + mode, because inferior will not executed. + Then set it to terminal_ours to make GDB get the signal. */ + target_terminal_ours (); + + /* In EXEC_FORWARD mode, record_list points to the tail of prev + instruction. */ + if (execution_direction == EXEC_FORWARD && record_list->next) + record_list = record_list->next; + + /* Loop over the record_list, looking for the next place to + stop. */ + do + { + /* Check for beginning and end of log. */ + if (execution_direction == EXEC_REVERSE + && record_list == &record_first) + { + /* Hit beginning of record log in reverse. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + if (execution_direction != EXEC_REVERSE && !record_list->next) + { + /* Hit end of record log going forward. */ + status->kind = TARGET_WAITKIND_NO_HISTORY; + break; + } + + /* Set ptid, register and memory according to record_list. */ + if (record_list->type == record_reg) + { + /* reg */ + gdb_byte reg[MAX_REGISTER_SIZE]; + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (record_list), + record_list->u.reg.num); + regcache_cooked_read (regcache, record_list->u.reg.num, reg); + regcache_cooked_write (regcache, record_list->u.reg.num, + record_list->u.reg.val); + memcpy (record_list->u.reg.val, reg, MAX_REGISTER_SIZE); + } + else if (record_list->type == record_mem) + { + /* mem */ + gdb_byte *mem = alloca (record_list->u.mem.len); + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = 0x%s len = %d.\n", + host_address_to_string (record_list), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory + (record_list->u.mem.addr, mem, record_list->u.mem.len)) + error (_("Process record: error reading memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + if (target_write_memory + (record_list->u.mem.addr, record_list->u.mem.val, + record_list->u.mem.len)) + error (_ + ("Process record: error writing memory at " + "addr = 0x%s len = %d."), + paddr_nz (record_list->u.mem.addr), + record_list->u.mem.len); + + memcpy (record_list->u.mem.val, mem, record_list->u.mem.len); + } + else + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_end %s to " + "inferior need_dasm = %d.\n", + host_address_to_string (record_list), + record_list->u.need_dasm); + + if (execution_direction == EXEC_FORWARD) + need_dasm = record_list->u.need_dasm; + if (need_dasm) + gdbarch_process_record_dasm (current_gdbarch); + + if (first_record_end && execution_direction == EXEC_REVERSE) + { + /* When reverse excute, the first record_end is the part of + current instruction. */ + first_record_end = 0; + } + else + { + /* In EXEC_REVERSE mode, this is the record_end of prev + instruction. + In EXEC_FORWARD mode, this is the record_end of current + instruction. */ + /* step */ + if (record_resume_step) + { + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: step.\n"); + continue_flag = 0; + } + + /* check breakpoint */ + tmp_pc = regcache_read_pc (regcache); + if (breakpoint_inserted_here_p (tmp_pc)) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + "Process record: break " + "at 0x%s.\n", + paddr_nz (tmp_pc)); + if (gdbarch_decr_pc_after_break (get_regcache_arch (regcache)) + && execution_direction == EXEC_FORWARD + && !record_resume_step) + regcache_write_pc (regcache, + tmp_pc + + gdbarch_decr_pc_after_break + (get_regcache_arch (regcache))); + continue_flag = 0; + } + } + if (execution_direction == EXEC_REVERSE) + need_dasm = record_list->u.need_dasm; + } + +next: + if (continue_flag) + { + if (execution_direction == EXEC_REVERSE) + { + if (record_list->prev) + record_list = record_list->prev; + } + else + { + if (record_list->next) + record_list = record_list->next; + } + } + } + while (continue_flag); + + signal (SIGINT, handle_sigint); + +replay_out: + if (record_get_sig) + status->value.sig = TARGET_SIGNAL_INT; + else + status->value.sig = TARGET_SIGNAL_TRAP; + + discard_cleanups (old_cleanups); + } + + do_cleanups (set_cleanups); + return inferior_ptid; +} + +static void +record_disconnect (struct target_ops *target, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); + + unpush_target (&record_ops); + target_disconnect (args, from_tty); +} + +static void +record_detach (struct target_ops *ops, char *args, int from_tty) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); + + unpush_target (&record_ops); + target_detach (args, from_tty); +} + +static void +record_mourn_inferior (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: " + "record_mourn_inferior\n"); + + unpush_target (&record_ops); + target_mourn_inferior (); +} + +/* Close process record target before killing the inferior process. */ +static void +record_kill (void) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); + + unpush_target (&record_ops); + target_kill (); +} + +/* Record registers change (by user or by GDB) to list as an instruction. */ +static void +record_registers_change (struct regcache *regcache, int regnum) +{ + /* Check record_insn_num. */ + record_check_insn_num (0); + + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + + record_regcache = get_current_regcache (); + + if (regnum < 0) + { + int i; + for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) + { + if (record_arch_list_add_reg (i)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + } + else + { + if (record_arch_list_add_reg (regnum)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + error (_("Process record: failed to record execution log.")); + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; +} + +static void +record_store_registers (struct target_ops *ops, struct regcache *regcache, + int regno) +{ + if (!record_gdb_operation_disable) + { + if (RECORD_IS_REPLAY) + { + int n; + struct cleanup *old_cleanups; + + /* Let user choose if he wants to write register or not. */ + if (regno < 0) + n = + nquery (_("Because GDB is in replay mode, changing the " + "value of a register will make the execution " + "log unusable from this point onward. " + "Change all registers?")); + else + n = + nquery (_("Because GDB is in replay mode, changing the value " + "of a register will make the execution log unusable " + "from this point onward. Change register %s?"), + gdbarch_register_name (get_regcache_arch (regcache), + regno)); + + if (!n) + { + /* Invalidate the value of regcache that was set in function + "regcache_raw_write". */ + if (regno < 0) + { + int i; + for (i = 0; + i < gdbarch_num_regs (get_regcache_arch (regcache)); + i++) + regcache_invalidate (regcache, i); + } + else + regcache_invalidate (regcache, regno); + + error (_("Process record canceled the operation.")); + } + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + record_registers_change (regcache, regno); + } + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, + regcache, regno); +} + +/* record_xfer_partial -- behavior is conditional on RECORD_IS_REPLAY. + In replay mode, we cannot write memory unles we are willing to + invalidate the record/replay log from this point forward. */ + +static LONGEST +record_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte * readbuf, + const gdb_byte * writebuf, ULONGEST offset, LONGEST len) +{ + if (!record_gdb_operation_disable + && (object == TARGET_OBJECT_MEMORY + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + { + if (RECORD_IS_REPLAY) + { + /* Let user choose if he wants to write memory or not. */ + if (!nquery (_("Because GDB is in replay mode, writing to memory " + "will make the execution log unusable from this " + "point onward. Write memory at address 0x%s?"), + paddr_nz (offset))) + return -1; + + /* Destroy the record from here forward. */ + record_list_release_next (); + } + + /* Check record_insn_num */ + record_check_insn_num (0); + + /* Record registers change to list as an instruction. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + if (record_arch_list_add_mem (offset, len)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + if (record_arch_list_add_end (0)) + { + record_list_release (record_arch_list_tail); + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Process record: failed to record " + "execution log.")); + return -1; + } + record_list->next = record_arch_list_head; + record_arch_list_head->prev = record_list; + record_list = record_arch_list_tail; + + if (record_insn_num == record_insn_max_num && record_insn_max_num) + record_list_release_first (); + else + record_insn_num++; + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +/* record_insert_breakpoint + record_remove_breakpoint + Behavior is conditional on RECORD_IS_REPLAY. + We will not actually insert or remove breakpoints when replaying, + nor when recording. */ + +static int +record_insert_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_insert_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_remove_breakpoint (struct bp_target_info *bp_tgt) +{ + if (!RECORD_IS_REPLAY) + { + struct cleanup *old_cleanups = record_gdb_operation_disable_set (); + int ret = record_beneath_to_remove_breakpoint (bp_tgt); + + do_cleanups (old_cleanups); + + return ret; + } + + return 0; +} + +static int +record_can_execute_reverse (void) +{ + return 1; +} + +static void +init_record_ops (void) +{ + record_ops.to_shortname = "record"; + record_ops.to_longname = "Process record and replay target"; + record_ops.to_doc = + "Log program while executing and replay execution from log."; + record_ops.to_open = record_open; + record_ops.to_close = record_close; + record_ops.to_resume = record_resume; + record_ops.to_wait = record_wait; + record_ops.to_disconnect = record_disconnect; + record_ops.to_detach = record_detach; + record_ops.to_mourn_inferior = record_mourn_inferior; + record_ops.to_kill = record_kill; + record_ops.to_create_inferior = find_default_create_inferior; + record_ops.to_store_registers = record_store_registers; + record_ops.to_xfer_partial = record_xfer_partial; + record_ops.to_insert_breakpoint = record_insert_breakpoint; + record_ops.to_remove_breakpoint = record_remove_breakpoint; + record_ops.to_can_execute_reverse = record_can_execute_reverse; + record_ops.to_stratum = record_stratum; + record_ops.to_magic = OPS_MAGIC; +} + +static void +show_record_debug (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + fprintf_filtered (file, _("Debugging of process record target is %s.\n"), + value); +} + +/* cmd_record_start -- alias for "target record". */ + +static void +cmd_record_start (char *args, int from_tty) +{ + execute_command ("target record", from_tty); +} + +/* cmd_record_delete -- truncate the record log from the present point + of replay until the end. */ + +static void +cmd_record_delete (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (RECORD_IS_REPLAY) + { + if (!from_tty || query (_("Delete the log from this point forward " + "and begin to record the running message " + "at current PC?"))) + record_list_release_next (); + } + else + printf_unfiltered (_("Already at end of record list.\n")); + + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* cmd_record_stop -- implement the "stoprecord" command. */ + +static void +cmd_record_stop (char *args, int from_tty) +{ + if (TARGET_IS_PROCESS_RECORD) + { + if (!record_list || !from_tty || query (_("Delete recorded log and " + "stop recording?"))) + unpush_target (&record_ops); + } + else + printf_unfiltered (_("Process record is not started.\n")); +} + +/* set_record_insn_max_num -- set upper limit of record log size. */ + +static void +set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) +{ + if (record_insn_num > record_insn_max_num && record_insn_max_num) + { + printf_unfiltered (_("Record instructions number is bigger than " + "record instructions max number. Auto delete " + "the first ones?\n")); + + while (record_insn_num > record_insn_max_num) + record_list_release_first (); + } +} + +/* show_record_insn_number -- print the current index + into the record log (number of insns recorded so far). */ + +static void +show_record_insn_number (char *ignore, int from_tty) +{ + printf_unfiltered (_("Record instruction number is %d.\n"), + record_insn_num); +} + +void +_initialize_record (void) +{ + /* Init record_maskall. */ + if (sigfillset (&record_maskall) == -1) + perror_with_name (_("Process record: sigfillset failed")); + + /* Init record_first. */ + record_first.prev = NULL; + record_first.next = NULL; + record_first.type = record_end; + record_first.u.need_dasm = 0; + + init_record_ops (); + add_target (&record_ops); + + add_setshow_zinteger_cmd ("record", no_class, &record_debug, + _("Set debugging of record/replay feature."), + _("Show debugging of record/replay feature."), + _("When enabled, debugging output for " + "record/replay feature is displayed."), + NULL, show_record_debug, &setdebuglist, + &showdebuglist); + + add_com ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command.")); + + add_com_alias ("rec", "record", class_obscure, 1); + + /* XXX: I try to use some simple commands such as "disconnect" and + "detach" to support this functions. But these commands all have + other affect to GDB such as call function "no_shared_libraries". + So I add special commands to GDB. */ + add_com ("delrecord", class_obscure, cmd_record_delete, + _("Delete the rest of execution log and start recording it anew.")); + add_com_alias ("dr", "delrecord", class_obscure, 1); + add_com ("stoprecord", class_obscure, cmd_record_stop, + _("Stop the record/replay target.")); + add_com_alias ("sr", "stoprecord", class_obscure, 1); + + /* Record instructions number limit command. */ + add_setshow_boolean_cmd ("record-stop-at-limit", no_class, + &record_stop_at_limit, + _("Set whether record/replay stop when " + "record/replay buffer becomes full."), + _("Show whether record/replay stop when " + "record/replay buffer becomes full."), + _("Enable is default value.\n" + "When enabled, if the record/replay buffer " + "becomes full,\n" + "ask user what to do.\n" + "When disabled, if the record/replay buffer " + "becomes full,\n" + "delete it and start new recording."), + NULL, NULL, &setlist, &showlist); + add_setshow_zinteger_cmd ("record-insn-number-max", no_class, + &record_insn_max_num, + _("Set record/replay buffer limit."), + _("Show record/replay buffer limit."), + _("Set the maximum number of instructions to be " + "stored in the\n" + "record/replay buffer. " + "Zero means unlimited (default 200000)."), + set_record_insn_max_num, + NULL, &setlist, &showlist); + add_info ("record-insn-number", show_record_insn_number, + _("Show the current number of instructions in the " + "record/replay buffer.")); +} --- /dev/null +++ b/record.h @@ -0,0 +1,87 @@ +/* Process record and replay target for GDB, the GNU debugger. + + Copyright (C) 2008 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/>. */ + +#ifndef _RECORD_H_ +#define _RECORD_H_ + +#define TARGET_IS_PROCESS_RECORD \ + (current_target.beneath == &record_ops) +#define RECORD_IS_REPLAY \ + (record_list->next || execution_direction == EXEC_REVERSE) + +typedef struct record_reg_s +{ + int num; + gdb_byte *val; +} record_reg_t; + +typedef struct record_mem_s +{ + CORE_ADDR addr; + int len; + gdb_byte *val; +} record_mem_t; + +enum record_type +{ + record_end = 0, + record_reg, + record_mem +}; + +/* This is the core struct of record function. + + An entity of record_t is a record of the value change of a register + ("record_reg") or a part of memory ("record_mem"). And each + instruction must has a record_t ("record_end") that points out this + is the last record_t of this instruction. + + Each record_t is linked to "record_list" by "prev" and "next". + */ +typedef struct record_s +{ + struct record_s *prev; + struct record_s *next; + enum record_type type; + union + { + /* reg */ + record_reg_t reg; + /* mem */ + record_mem_t mem; + /* end */ + int need_dasm; + } u; +} record_t; + +extern int record_debug; +extern record_t *record_list; +extern record_t *record_arch_list_head; +extern record_t *record_arch_list_tail; +extern struct regcache *record_regcache; + +extern struct target_ops record_ops; + +extern int record_arch_list_add_reg (int num); +extern int record_arch_list_add_mem (CORE_ADDR addr, int len); +extern int record_arch_list_add_end (int need_dasm); +extern void record_message (struct gdbarch *gdbarch); +extern struct cleanup * record_gdb_operation_disable_set (void); + +#endif /* _RECORD_H_ */ ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [RFA] Submit process record and replay third time, 3/9 2009-02-23 14:08 ` teawater @ 2009-02-28 10:02 ` teawater 0 siblings, 0 replies; 38+ messages in thread From: teawater @ 2009-02-28 10:02 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches Hi Pedro, Sorry to disturb you. Could you send me your comment to me when you find it. Then I can do it at once. It will help me a lot. :) Thanks, Hui On Mon, Feb 23, 2009 at 16:56, teawater <teawater@gmail.com> wrote: > > Hi Pedro, > > 3-record_target.txt patch update follow your "Modernize solaris > threads support" patch. > > Thanks, > Hui > > On Tue, Feb 17, 2009 at 15:01, teawater <teawater@gmail.com> wrote: > > Update follow the cvs-head. It just has a very small change - add a 0 > > to be the argument of observer_notify_normal_stop. > > > > On Sun, Feb 8, 2009 at 21:03, teawater <teawater@gmail.com> wrote: > >> Hi Pedro, > >> > >> According to your mail "Get rid of linux-thread-db.c:target_beneath". > >> I get ride of this global beneath function pointers. > >> > >> Do you think this patch is OK? > >> > >> Thanks, > >> Hui > >> > > ^ permalink raw reply [flat|nested] 38+ messages in thread
end of thread, other threads:[~2009-03-18 13:14 UTC | newest] Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2009-01-08 5:46 [RFA] Submit process record and replay third time, 3/9 teawater 2009-01-13 3:06 ` teawater 2009-01-20 18:32 ` Marc Khouzam 2009-01-20 19:47 ` Marc Khouzam 2009-01-21 0:20 ` teawater 2009-01-21 2:53 ` teawater 2009-01-22 13:23 ` Pedro Alves 2009-01-22 15:23 ` teawater 2009-01-22 22:33 ` Pedro Alves 2009-01-22 22:36 ` Pedro Alves 2009-01-23 0:00 ` teawater 2009-01-23 6:58 ` teawater 2009-01-23 14:56 ` teawater 2009-01-23 15:34 ` Pedro Alves 2009-01-23 15:55 ` teawater 2009-02-02 9:05 ` teawater 2009-02-08 13:03 ` teawater 2009-02-17 7:12 ` teawater 2009-02-17 7:21 ` teawater 2009-02-23 16:05 ` teawater 2009-03-03 20:40 ` Pedro Alves 2009-03-04 3:42 ` teawater 2009-03-09 6:01 ` teawater 2009-03-09 19:31 ` Pedro Alves 2009-03-10 17:03 ` teawater 2009-03-09 20:35 ` Pedro Alves 2009-03-10 17:32 ` teawater 2009-03-10 19:35 ` Pedro Alves 2009-03-11 1:15 ` teawater 2009-03-13 0:27 ` teawater 2009-03-16 11:21 ` teawater 2009-03-18 8:50 ` teawater 2009-03-18 13:12 ` teawater 2009-03-18 13:05 ` teawater 2009-03-18 13:14 ` teawater 2009-03-18 13:54 ` teawater 2009-02-23 14:08 ` teawater 2009-02-28 10:02 ` teawater
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox