* Re: [RFA/RFC] Add dump and load command to process record and replay @ 2022-01-21 6:46 Simon Sobisch via Gdb-patches 2022-01-24 9:26 ` Hui Zhu via Gdb-patches 0 siblings, 1 reply; 51+ messages in thread From: Simon Sobisch via Gdb-patches @ 2022-01-21 6:46 UTC (permalink / raw) To: gdb-patches; +Cc: Michael Snyder This RFC was about a way to store a command file along with recording and later be able to load it and reverse-step / step through it. That idea sounds very cool and like something that I would use quite often, especially on longer debugging runs where the starting point takes a while to reach... I've stumbled over this suggested patch which was discussed and adjusted multiple times in 2009 and then seem to be loose on a documentation question. The last entry I've found to this was https://sourceware.org/legacy-ml/gdb-patches/2009-09/msg00145.html Can anyone share about the state of this patch? Would anyone like to adjust this to the current source? Thanks for any insights, Simon ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2022-01-21 6:46 [RFA/RFC] Add dump and load command to process record and replay Simon Sobisch via Gdb-patches @ 2022-01-24 9:26 ` Hui Zhu via Gdb-patches 2022-04-13 12:21 ` Simon Sobisch via Gdb-patches 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu via Gdb-patches @ 2022-01-24 9:26 UTC (permalink / raw) To: Simon Sobisch; +Cc: Michael Snyder, gdb-patches ml I am not sure. I gave up the patches If you cannot find it in the upstream. Best, Hui Simon Sobisch <simonsobisch@gnu.org> 于2022年1月21日周五 14:46写道: > > This RFC was about a way to store a command file along with recording > and later be able to load it and reverse-step / step through it. > That idea sounds very cool and like something that I would use quite > often, especially on longer debugging runs where the starting point > takes a while to reach... > > I've stumbled over this suggested patch which was discussed and adjusted > multiple times in 2009 and then seem to be loose on a documentation > question. > > The last entry I've found to this was > https://sourceware.org/legacy-ml/gdb-patches/2009-09/msg00145.html > > Can anyone share about the state of this patch? > Would anyone like to adjust this to the current source? > > Thanks for any insights, > Simon ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2022-01-24 9:26 ` Hui Zhu via Gdb-patches @ 2022-04-13 12:21 ` Simon Sobisch via Gdb-patches 0 siblings, 0 replies; 51+ messages in thread From: Simon Sobisch via Gdb-patches @ 2022-04-13 12:21 UTC (permalink / raw) To: gdb-patches ml; +Cc: Michael Snyder Can anyone share information about the following points? * Was there any showstopper issue with the patch? * Would someone take up Hui's work on this? Simon Am 24.01.2022 um 10:26 schrieb Hui Zhu: > I am not sure. > I gave up the patches If you cannot find it in the upstream. > > Best, > Hui > > Simon Sobisch <simonsobisch@gnu.org> 于2022年1月21日周五 14:46写道: >> >> This RFC was about a way to store a command file along with recording >> and later be able to load it and reverse-step / step through it. >> That idea sounds very cool and like something that I would use quite >> often, especially on longer debugging runs where the starting point >> takes a while to reach... >> >> I've stumbled over this suggested patch which was discussed and adjusted >> multiple times in 2009 and then seem to be loose on a documentation >> question. >> >> The last entry I've found to this was >> https://sourceware.org/legacy-ml/gdb-patches/2009-09/msg00145.html >> >> Can anyone share about the state of this patch? >> Would anyone like to adjust this to the current source? >> >> Thanks for any insights, >> Simon > ^ permalink raw reply [flat|nested] 51+ messages in thread
* [RFA/RFC] Add dump and load command to process record and replay
@ 2009-08-01 7:31 Hui Zhu
2009-08-01 9:57 ` Eli Zaretskii
2009-08-01 19:20 ` Michael Snyder
0 siblings, 2 replies; 51+ messages in thread
From: Hui Zhu @ 2009-08-01 7:31 UTC (permalink / raw)
To: gdb-patches ml; +Cc: Michael Snyder, Eli Zaretskii
[-- Attachment #1: Type: text/plain, Size: 40055 bytes --]
Hi,
This patch add command record dump and record load to make prec can
dump execution log to a file and load it later. And load can work
with core file.
And I include "mem_entry_not_accessible"(not_replay) patch to this
patch because when replay with core file, some address in lib cannot
be read and write.
For example:
./gdb ./a.out
GNU gdb (GDB) 6.8.50.20090801-cvs
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Setting up the environment for debugging gdb.
Function "internal_error" not defined.
Make breakpoint pending on future shared library load? (y or [n])
[answered N; input not from terminal]
Function "info_command" not defined.
Make breakpoint pending on future shared library load? (y or [n])
[answered N; input not from terminal]
/home/teawater/gdb/bgdbno/gdb/.gdbinit:8: Error in sourced command file:
No breakpoint number 0.
(gdb) start
Temporary breakpoint 1 at 0x80483c1: file 1.c, line 20.
Starting program: /home/teawater/gdb/bgdbno/gdb/a.out
Temporary breakpoint 1, main () at 1.c:20
20 int b = 0;
(gdb) gcore
Saved corefile core.11945
(gdb) record
(gdb) n
During symbol reading, incomplete CFI data; unspecified registers
(e.g., eax) at 0x80483be.
21 int c = 1;
(gdb)
24 printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
a = 0 b = 0 c = 1
25 b = cool ();
(gdb)
a = 3
26 printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
a = 3 b = 3 c = 1
29 c += 1;
(gdb)
30 printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
a = 3 b = 3 c = 2
31 a -= 2;
(gdb)
32 printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
a = 1 b = 3 c = 2
35 return (0);
(gdb)
36 }
(gdb) rc
Continuing.
No more reverse-execution history.
main () at 1.c:20
20 int b = 0;
(gdb) record dump
Saved recfile rec.11945.
(gdb) quit
After that, you get core.11945 and rec.11945.
Then you can replay 11945 execution log with them.
./gdb ./a.out ./core.11945
GNU gdb (GDB) 6.8.50.20090801-cvs
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `/home/teawater/gdb/bgdbno/gdb/a.out'.
Program terminated with signal 5, Trace/breakpoint trap.
#0 main () at 1.c:20
20 int b = 0;
Setting up the environment for debugging gdb.
Function "internal_error" not defined.
Make breakpoint pending on future shared library load? (y or [n])
[answered N; input not from terminal]
Function "info_command" not defined.
Make breakpoint pending on future shared library load? (y or [n])
[answered N; input not from terminal]
/home/teawater/gdb/bgdbno/gdb/.gdbinit:8: Error in sourced command file:
No breakpoint number 0.
(gdb) record load ./rec.11945
Auto start process record.
Loaded recfile ./rec.11945.
(gdb) p b
During symbol reading, incomplete CFI data; unspecified registers
(e.g., eax) at 0x80483be.
$1 = -1208017488
(gdb) n
21 int c = 1;
(gdb) p b
$2 = 0
(gdb) n
24 printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
25 b = cool ();
(gdb) p b
$3 = 0
(gdb) n
26 printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb)
29 c += 1;
(gdb) p c
$4 = 1
(gdb) n
30 printf ("a = %d b = %d c = %d\n", a, b, c);
(gdb) p c
$5 = 2
(gdb) c
Continuing.
No more reverse-execution history.
main () at 1.c:36
36 }
(gdb) rc
Continuing.
No more reverse-execution history.
main () at 1.c:20
20 int b = 0;
(gdb) p c
$6 = 134513849
(gdb) quit
The program is running. Quit anyway (and kill it)? (y or n) y
Please send your comments about it. Thanks a lot.
Hui
2009-08-01 Hui Zhu <teawater@gmail.com>
Add dump and load command to process record and replay.
* record.c (completer.h, arch-utils.h, gdbcore.h, exec.h,
byteswap.h, netinet/in.h): Include files.
(RECORD_IS_REPLAY): Return true if record_core is true.
(RECORD_FILE_MAGIC): New macro.
(record_mem_entry): New field 'mem_entry_not_accessible'.
(record_core_buf_entry): New struct.
(record_core, record_core_regbuf, record_core_start,
record_core_end, record_core_buf_list,
record_beneath_to_fetch_registers_ops,
record_beneath_to_fetch_registers,
record_beneath_to_store_registers_ops,
record_beneath_to_store_registers,
record_beneath_to_has_execution_ops,
record_beneath_to_has_execution,
record_beneath_to_prepare_to_store): New variables.
(record_list_release_first_insn): Change function
record_list_release_first to this name.
(record_arch_list_add_mem): Initialize
mem_entry_not_accessible to 0.
(record_arch_list_cleanups): New function.
(record_message_cleanups): Removed.
(record_message): Change to call record_arch_list_cleanups
and record_list_release_first_insn.
(record_exec_entry): New function.
(record_open): Add support for target core.
(record_close): Add support for target core.
(record_wait): Call record_exec_entry.
(record_kill): Add support for target core.
(record_registers_change): Call record_list_release_first_insn.
(record_fetch_registers): New function.
(record_prepare_to_store): New function.
(record_store_registers): Add support for target core.
(record_xfer_partial): Add support for target core.
(record_has_execution): New function.
(init_record_ops): Set record_ops.to_fetch_registers,
record_ops.to_prepare_to_store
and record_ops.to_has_execution.
(cmd_record_fd_cleanups): New function.
(cmd_record_dump): New function.
(cmd_record_load): New function.
(set_record_insn_max_num): Call record_list_release_first_insn.
(_initialize_record): Add commands "record dump"
and "record load".
---
record.c | 709 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 641 insertions(+), 68 deletions(-)
--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
#include "gdbthread.h"
#include "event-top.h"
#include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
#include "record.h"
+#include <byteswap.h>
#include <signal.h>
+#include <netinet/in.h>
#define DEFAULT_RECORD_INSN_MAX_NUM 200000
#define RECORD_IS_REPLAY \
- (record_list->next || execution_direction == EXEC_REVERSE)
+ (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC htonl(0x20090726)
/* These are the core struct of record function.
@@ -51,6 +59,7 @@ struct record_mem_entry
{
CORE_ADDR addr;
int len;
+ int mem_entry_not_accessible;
gdb_byte *val;
};
@@ -75,9 +84,23 @@ struct record_entry
} u;
};
+struct record_core_buf_entry
+{
+ struct record_core_buf_entry *prev;
+ struct target_section *p;
+ bfd_byte *buf;
+};
+
/* This is the debug switch for process record. */
int record_debug = 0;
+/* Record with core target. */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
/* These list is for execution log. */
static struct record_entry record_first;
static struct record_entry *record_list = &record_first;
@@ -100,6 +123,14 @@ static struct target_ops *record_beneath
static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
struct target_waitstatus *,
int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+ struct regcache *,
+ int regno);
+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_store_registers_ops;
static void (*record_beneath_to_store_registers) (struct target_ops *,
struct regcache *,
@@ -116,6 +147,9 @@ static int (*record_beneath_to_insert_br
struct bp_target_info *);
static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);
static void
record_list_release (struct record_entry *rec)
@@ -166,7 +200,7 @@ record_list_release_next (void)
}
static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
{
struct record_entry *tmp = NULL;
enum record_type type;
@@ -275,6 +309,7 @@ record_arch_list_add_mem (CORE_ADDR addr
rec->type = record_mem;
rec->u.mem.addr = addr;
rec->u.mem.len = len;
+ rec->u.mem.mem_entry_not_accessible = 0;
if (target_read_memory (addr, rec->u.mem.val, len))
{
@@ -336,30 +371,30 @@ record_check_insn_num (int set_terminal)
if (q)
record_stop_at_limit = 0;
else
- error (_("Process record: inferior program stopped."));
+ error (_("Process record: stoped by user."));
}
}
}
}
+static void
+record_arch_list_cleanups (void *ignore)
+{
+ record_list_release (record_arch_list_tail);
+}
+
/* Before inferior step (when GDB record the running message, inferior
only can step), GDB will call this function to record the values to
record_list. This function will call gdbarch_process_record to
record the running message of inferior and set them to
record_arch_list, and add it to record_list. */
-static void
-record_message_cleanups (void *ignore)
-{
- record_list_release (record_arch_list_tail);
-}
-
static int
record_message (void *args)
{
int ret;
struct regcache *regcache = args;
- struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+ struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
record_arch_list_head = NULL;
record_arch_list_tail = NULL;
@@ -382,7 +417,7 @@ record_message (void *args)
record_list = record_arch_list_tail;
if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
+ record_list_release_first_insn ();
else
record_insn_num++;
@@ -412,6 +447,92 @@ record_gdb_operation_disable_set (void)
return old_cleanups;
}
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+ struct record_entry *entry)
+{
+ switch (entry->type)
+ {
+ case record_reg: /* reg */
+ {
+ gdb_byte reg[MAX_REGISTER_SIZE];
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_reg %s to "
+ "inferior num = %d.\n",
+ host_address_to_string (entry),
+ entry->u.reg.num);
+
+ regcache_cooked_read (regcache, entry->u.reg.num, reg);
+ regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+ memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+ }
+ break;
+ case record_mem: /* mem */
+ {
+ if (record_list->u.mem.mem_entry_not_accessible)
+ record_list->u.mem.mem_entry_not_accessible = 0;
+ else
+ {
+ gdb_byte *mem = alloca (entry->u.mem.len);
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_mem %s to "
+ "inferior addr = %s len = %d.\n",
+ host_address_to_string (entry),
+ paddress (gdbarch, entry->u.mem.addr),
+ record_list->u.mem.len);
+
+ if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+ {
+ if ((execution_direction == EXEC_REVERSE && !record_core)
+ || (execution_direction != EXEC_REVERSE && record_core))
+ {
+ record_list->u.mem.mem_entry_not_accessible = 1;
+ if (record_debug)
+ warning (_("Process record: error reading memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ else
+ error (_("Process record: error reading memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ else
+ {
+ if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+ entry->u.mem.len))
+ {
+ if ((execution_direction == EXEC_REVERSE && !record_core)
+ || (execution_direction != EXEC_REVERSE &&
record_core))
+ {
+ record_list->u.mem.mem_entry_not_accessible = 1;
+ if (record_debug)
+ warning (_("Process record: error writing memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ else
+ error (_("Process record: error writing memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ }
+
+ memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+ }
+ }
+ break;
+ }
+}
+
static void
record_open (char *name, int from_tty)
{
@@ -420,8 +541,13 @@ record_open (char *name, int from_tty)
if (record_debug)
fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+ if (!strcmp (current_target.to_shortname, "core"))
+ record_core = 1;
+ else
+ record_core = 0;
+
/* check exec */
- if (!target_has_execution)
+ if (!target_has_execution && !record_core)
error (_("Process record: the program is not being run."));
if (non_stop)
error (_("Process record target can't debug inferior in non-stop mode "
@@ -450,6 +576,8 @@ record_open (char *name, int from_tty)
record_beneath_to_xfer_partial = NULL;
record_beneath_to_insert_breakpoint = NULL;
record_beneath_to_remove_breakpoint = NULL;
+ record_beneath_to_has_execution = NULL;
+ record_beneath_to_prepare_to_store = NULL;
/* Set the beneath function pointers. */
for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -464,6 +592,11 @@ record_open (char *name, int from_tty)
record_beneath_to_wait = t->to_wait;
record_beneath_to_wait_ops = t;
}
+ if (!record_beneath_to_fetch_registers)
+ {
+ record_beneath_to_fetch_registers = t->to_fetch_registers;
+ record_beneath_to_fetch_registers_ops = t;
+ }
if (!record_beneath_to_store_registers)
{
record_beneath_to_store_registers = t->to_store_registers;
@@ -478,19 +611,51 @@ record_open (char *name, int from_tty)
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_has_execution)
+ {
+ record_beneath_to_has_execution_ops = t;
+ record_beneath_to_has_execution = t->to_has_execution;
+ }
+ if (!record_beneath_to_prepare_to_store)
+ record_beneath_to_prepare_to_store = t->to_prepare_to_store;
}
- if (!record_beneath_to_resume)
+ if (!record_beneath_to_resume && !record_core)
error (_("Process record can't get to_resume."));
- if (!record_beneath_to_wait)
+ if (!record_beneath_to_wait && !record_core)
error (_("Process record can't get to_wait."));
- if (!record_beneath_to_store_registers)
+ if (!record_beneath_to_fetch_registers)
+ error (_("Process record can't get to_fetch_registers."));
+ if (!record_beneath_to_store_registers && !record_core)
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)
+ if (!record_beneath_to_insert_breakpoint && !record_core)
error (_("Process record can't get to_insert_breakpoint."));
- if (!record_beneath_to_remove_breakpoint)
+ if (!record_beneath_to_remove_breakpoint && !record_core)
error (_("Process record can't get to_remove_breakpoint."));
+ if (!record_beneath_to_has_execution && !record_core)
+ error (_("Process record can't get to_has_execution."));
+ if (!record_beneath_to_prepare_to_store && !record_core)
+ error (_("Process record can't get to_prepare_to_store."));
+
+ if (record_core)
+ {
+ /* Get record_core_regbuf. */
+ struct regcache *regcache = get_current_regcache ();
+ int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+ int i;
+
+ target_fetch_registers (regcache, -1);
+ record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+ for (i = 0; i < regnum; i ++)
+ regcache_raw_collect (regcache, i,
+ record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+ /* Get record_core_start and record_core_end. */
+ if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+ error (_("\"%s\": Can't find sections: %s"),
+ bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+ }
push_target (&record_ops);
@@ -503,10 +668,26 @@ record_open (char *name, int from_tty)
static void
record_close (int quitting)
{
+ struct record_core_buf_entry *entry;
+
if (record_debug)
fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
record_list_release (record_list);
+
+ /* Release record_core_regbuf. */
+ xfree (record_core_regbuf);
+
+ /* Release record_core_buf_list. */
+ if (record_core_buf_list)
+ {
+ for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+ {
+ xfree (record_core_buf_list);
+ record_core_buf_list = entry;
+ }
+ record_core_buf_list = NULL;
+ }
}
static int record_resume_step = 0;
@@ -708,53 +889,9 @@ record_wait (struct target_ops *ops,
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 = %s len = %d.\n",
- host_address_to_string (record_list),
- paddress (gdbarch, 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 = %s len = %d."),
- paddress (gdbarch, 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 = %s len = %d."),
- paddress (gdbarch, record_list->u.mem.addr),
- record_list->u.mem.len);
+ record_exec_entry (regcache, gdbarch, record_list);
- memcpy (record_list->u.mem.val, mem, record_list->u.mem.len);
- }
- else
+ if (record_list->type == record_end)
{
if (record_debug > 1)
fprintf_unfiltered (gdb_stdlog,
@@ -874,7 +1011,9 @@ record_kill (struct target_ops *ops)
fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
unpush_target (&record_ops);
- target_kill ();
+
+ if (!record_core)
+ target_kill ();
}
/* Record registers change (by user or by GDB) to list as an instruction. */
@@ -918,15 +1057,58 @@ record_registers_change (struct regcache
record_list = record_arch_list_tail;
if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
+ record_list_release_first_insn ();
else
record_insn_num++;
}
static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+ int regno)
+{
+ if (record_core)
+ {
+ if (regno < 0)
+ {
+ int num = gdbarch_num_regs (get_regcache_arch (regcache));
+ int i;
+
+ for (i = 0; i < num; i ++)
+ regcache_raw_supply (regcache, i,
+ record_core_regbuf + MAX_REGISTER_SIZE * i);
+ }
+ else
+ regcache_raw_supply (regcache, regno,
+ record_core_regbuf + MAX_REGISTER_SIZE * regno);
+ }
+ else
+ record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+ regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+ if (!record_core)
+ record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
record_store_registers (struct target_ops *ops, struct regcache *regcache,
int regno)
{
+ if (record_core)
+ {
+ /* Debug with core. */
+ if (record_gdb_operation_disable)
+ regcache_raw_collect (regcache, regno,
+ record_core_regbuf + MAX_REGISTER_SIZE * regno);
+ else
+ error (_("You can't do that without a process to debug."));
+
+ return;
+ }
+
if (!record_gdb_operation_disable)
{
if (RECORD_IS_REPLAY)
@@ -973,6 +1155,7 @@ record_store_registers (struct target_op
record_registers_change (regcache, regno);
}
+
record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
regcache, regno);
}
@@ -988,7 +1171,7 @@ record_xfer_partial (struct target_ops *
{
if (!record_gdb_operation_disable
&& (object == TARGET_OBJECT_MEMORY
- || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+ || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
{
if (RECORD_IS_REPLAY)
{
@@ -1032,11 +1215,91 @@ record_xfer_partial (struct target_ops *
record_list = record_arch_list_tail;
if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
+ record_list_release_first_insn ();
else
record_insn_num++;
}
+ if (record_core && object == TARGET_OBJECT_MEMORY)
+ {
+ /* Debug with core. */
+ if (record_gdb_operation_disable || !writebuf)
+ {
+ struct target_section *p;
+ for (p = record_core_start; p < record_core_end; p++)
+ {
+ if (offset >= p->addr)
+ {
+ struct record_core_buf_entry *entry;
+
+ if (offset >= p->endaddr)
+ continue;
+
+ if (offset + len > p->endaddr)
+ len = p->endaddr - offset;
+
+ offset -= p->addr;
+
+ /* Read readbuf or write writebuf p, offset, len. */
+ /* Check flags. */
+ if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+ || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+ {
+ if (readbuf)
+ memset (readbuf, 0, len);
+ return len;
+ }
+ /* Get record_core_buf_entry. */
+ for (entry = record_core_buf_list; entry;
+ entry = entry->prev)
+ if (entry->p == p)
+ break;
+ if (writebuf)
+ {
+ if (!entry)
+ {
+ /* Add a new entry. */
+ entry
+ = (struct record_core_buf_entry *)
+ xmalloc
+ (sizeof (struct record_core_buf_entry));
+ entry->p = p;
+ if (!bfd_malloc_and_get_section (p->bfd,
+ p->the_bfd_section,
+ &entry->buf))
+ {
+ xfree (entry);
+ return 0;
+ }
+ entry->prev = record_core_buf_list;
+ record_core_buf_list = entry;
+ }
+
+ memcpy (entry->buf + offset, writebuf, (size_t) len);
+ }
+ else
+ {
+ if (!entry)
+ return record_beneath_to_xfer_partial
+ (record_beneath_to_xfer_partial_ops,
+ object, annex, readbuf, writebuf,
+ offset, len);
+
+ memcpy (readbuf, entry->buf + offset, (size_t) len);
+ }
+
+ return len;
+ }
+ }
+
+ return 0;
+ }
+ else
+ error (_("You can't do that without a process to debug."));
+
+ return 0;
+ }
+
return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
object, annex, readbuf, writebuf,
offset, len);
@@ -1086,6 +1349,15 @@ record_can_execute_reverse (void)
return 1;
}
+int
+record_has_execution (struct target_ops *ops)
+{
+ if (record_core)
+ return 1;
+
+ return record_beneath_to_has_execution (ops);
+}
+
static void
init_record_ops (void)
{
@@ -1102,11 +1374,14 @@ init_record_ops (void)
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_fetch_registers = record_fetch_registers;
+ record_ops.to_prepare_to_store = record_prepare_to_store;
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_has_execution = record_has_execution;
record_ops.to_stratum = record_stratum;
record_ops.to_magic = OPS_MAGIC;
}
@@ -1127,6 +1402,293 @@ cmd_record_start (char *args, int from_t
execute_command ("target record", from_tty);
}
+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+ int recfd = *(int *) recfdp;
+ close (recfd);
+}
+
+/* Dump the execution log to a file. */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+ char *recfilename, recfilename_buffer[40];
+ int recfd;
+ struct record_entry *cur_record_list;
+ uint32_t magic;
+ struct regcache *regcache;
+ struct gdbarch *gdbarch;
+ struct cleanup *old_cleanups;
+ struct cleanup *set_cleanups;
+
+ if (current_target.to_stratum != record_stratum)
+ error (_("Process record is not started.\n"));
+
+ if (args && *args)
+ recfilename = args;
+ else
+ {
+ /* Default corefile name is "rec.PID". */
+ sprintf (recfilename_buffer, "rec.%d", PIDGET (inferior_ptid));
+ recfilename = recfilename_buffer;
+ }
+
+ /* Open the dump file. */
+ recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+ S_IRUSR | S_IWUSR);
+ if (recfd < 0)
+ error (_("Failed to open '%s' for dump."), recfilename);
+ old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+ /* Save the current record entry to "cur_record_list". */
+ cur_record_list = record_list;
+
+ /* Get the values of regcache and gdbarch. */
+ regcache = get_current_regcache ();
+ gdbarch = get_regcache_arch (regcache);
+
+ /* Disable the GDB operation record. */
+ set_cleanups = record_gdb_operation_disable_set ();
+
+ /* Write the magic code. */
+ magic = RECORD_FILE_MAGIC;
+ if (write (recfd, &magic, 4) != 4)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ /* Reverse execute to the begin of record list. */
+ while (1)
+ {
+ /* Check for beginning and end of log. */
+ if (record_list == &record_first)
+ break;
+
+ record_exec_entry (regcache, gdbarch, record_list);
+
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+
+ /* Dump the entries to recfd and forward execute to the end of
+ record list. */
+ while (1)
+ {
+ /* Dump entry. */
+ if (record_list != &record_first)
+ {
+ uint8_t tmpu8;
+ uint64_t tmpu64;
+
+ tmpu8 = record_list->type;
+ if (write (recfd, &tmpu8, 1) != 1)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ switch (record_list->type)
+ {
+ case record_reg: /* reg */
+ tmpu64 = record_list->u.reg.num;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ if (write (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ if (write (recfd, record_list->u.reg.val,
+ MAX_REGISTER_SIZE) != MAX_REGISTER_SIZE)
+ error (_("Failed to write '%s' for dump."), recfilename);
+ break;
+ case record_mem: /* mem */
+ if (!record_list->u.mem.mem_entry_not_accessible)
+ {
+ tmpu64 = record_list->u.mem.addr;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ if (write (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ tmpu64 = record_list->u.mem.len;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ if (write (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ if (write (recfd, record_list->u.mem.val,
+ record_list->u.mem.len) != record_list->u.mem.len)
+ error (_("Failed to write '%s' for dump."), recfilename);
+ }
+ break;
+ }
+ }
+
+ /* Execute entry. */
+ record_exec_entry (regcache, gdbarch, record_list);
+
+ if (record_list->next)
+ record_list = record_list->next;
+ else
+ break;
+ }
+
+ /* Reverse execute to cur_record_list. */
+ while (1)
+ {
+ /* Check for beginning and end of log. */
+ if (record_list == cur_record_list)
+ break;
+
+ record_exec_entry (regcache, gdbarch, record_list);
+
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+
+ do_cleanups (set_cleanups);
+ do_cleanups (old_cleanups);
+
+ /* Succeeded. */
+ fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);
+}
+
+/* Load the execution log from a file. */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+ int recfd;
+ uint32_t magic;
+ struct cleanup *old_cleanups;
+ struct cleanup *old_cleanups2;
+ struct record_entry *rec;
+ int insn_number = 0;
+
+ if (current_target.to_stratum != record_stratum)
+ {
+ cmd_record_start (NULL, from_tty);
+ printf_unfiltered (_("Auto start process record.\n"));
+ }
+
+ if (!args || (args && !*args))
+ error (_("Argument for filename required.\n"));
+
+ /* Open the load file. */
+ recfd = open (args, O_RDONLY | O_BINARY);
+ if (recfd < 0)
+ error (_("Failed to open '%s' for load."), args);
+ old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+ /* Check the magic code. */
+ if (read (recfd, &magic, 4) != 4)
+ error (_("Failed to read '%s' for load."), args);
+ if (magic != RECORD_FILE_MAGIC)
+ error (_("'%s' is not a record dump."), args);
+
+ /* Load the entries in recfd to the record_arch_list_head and
+ record_arch_list_tail. */
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+ old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+ while (1)
+ {
+ int ret;
+ uint8_t tmpu8;
+ uint64_t tmpu64;
+
+ ret = read (recfd, &tmpu8, 1);
+ if (ret < 0)
+ error (_("Failed to read '%s' for load."), args);
+ if (ret == 0)
+ break;
+
+ switch (tmpu8)
+ {
+ case record_reg: /* reg */
+ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+ rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE);
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_reg;
+ /* Get num. */
+ if (read (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ rec->u.reg.num = tmpu64;
+ /* Get val. */
+ if (read (recfd, rec->u.reg.val,
+ MAX_REGISTER_SIZE) != MAX_REGISTER_SIZE)
+ error (_("Failed to read '%s' for load."), args);
+ record_arch_list_add (rec);
+ break;
+ case record_mem: /* mem */
+ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_mem;
+ /* Get addr. */
+ if (read (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ rec->u.mem.addr = tmpu64;
+ /* Get len. */
+ if (read (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ rec->u.mem.len = tmpu64;
+ rec->u.mem.mem_entry_not_accessible = 0;
+ rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+ /* Get val. */
+ if (read (recfd, rec->u.mem.val,
+ rec->u.mem.len) != rec->u.mem.len)
+ error (_("Failed to read '%s' for load."), args);
+ record_arch_list_add (rec);
+ break;
+
+ case record_end: /* end */
+ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_end;
+ record_arch_list_add (rec);
+ insn_number ++;
+ break;
+
+ default:
+ error (_("Format of '%s' is not right."), args);
+ break;
+ }
+ }
+
+ discard_cleanups (old_cleanups2);
+
+ /* Add record_arch_list_head to the end of record list. */
+ for (rec = record_list; rec->next; rec = rec->next);
+ rec->next = record_arch_list_head;
+ record_arch_list_head->prev = rec;
+
+ /* Update record_insn_num and record_insn_max_num. */
+ record_insn_num += insn_number;
+ if (record_insn_num > record_insn_max_num)
+ {
+ record_insn_max_num = record_insn_num;
+ warning (_("Auto increase record/replay buffer limit to %d."),
+ record_insn_max_num);
+ }
+
+ do_cleanups (old_cleanups);
+
+ /* Succeeded. */
+ fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
/* Truncate the record log from the present point
of replay until the end. */
@@ -1177,7 +1739,7 @@ set_record_insn_max_num (char *args, int
"the first ones?\n"));
while (record_insn_num > record_insn_max_num)
- record_list_release_first ();
+ record_list_release_first_insn ();
}
}
@@ -1217,6 +1779,8 @@ info_record_command (char *args, int fro
void
_initialize_record (void)
{
+ struct cmd_list_element *c;
+
/* Init record_first. */
record_first.prev = NULL;
record_first.next = NULL;
@@ -1250,6 +1814,15 @@ _initialize_record (void)
"info record ", 0, &infolist);
add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
+ c = add_cmd ("dump", class_obscure, cmd_record_dump,
+ _("Dump the execution log to a file.\n\
+Argument is optional filename. Default filename is 'rec.<process_id>'."),
+ &record_cmdlist);
+ set_cmd_completer (c, filename_completer);
+ c = add_cmd ("load", class_obscure, cmd_record_load,
+ _("Load the execution log from a file. Argument is filename."),
+ &record_cmdlist);
+ set_cmd_completer (c, filename_completer);
add_cmd ("delete", class_obscure, cmd_record_delete,
_("Delete the rest of execution log and start recording it anew."),
2009-08-01 Hui Zhu <teawater@gmail.com>
* gdb.texinfo (Process Record and Replay): Document the
"record dump" and "record dump" commands.
---
doc/gdb.texinfo | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5190,6 +5190,22 @@ When record target runs in replay mode (
subsequent execution log and begin to record a new execution log starting
from the current address. This means you will abandon the previously
recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Produce a record execution log of the inferior process. The optional
+argument @var{file} specifies the file name where to put the record dump.
+If not specified, the file name defaults to @file{rec.@var{pid}}, where
+@var{pid} is the inferior process ID.
+
+@kindex record load
+@kindex rec load
+@item record load @var{file}
+@itemx rec dump @var{file}
+Load process record execution log from @var{file}.
+It can work with @code{core-file}.
@end table
[-- Attachment #2: prec-dump.txt --]
[-- Type: text/plain, Size: 32602 bytes --]
---
record.c | 709 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 641 insertions(+), 68 deletions(-)
--- a/record.c
+++ b/record.c
@@ -23,14 +23,22 @@
#include "gdbthread.h"
#include "event-top.h"
#include "exceptions.h"
+#include "completer.h"
+#include "arch-utils.h"
+#include "gdbcore.h"
+#include "exec.h"
#include "record.h"
+#include <byteswap.h>
#include <signal.h>
+#include <netinet/in.h>
#define DEFAULT_RECORD_INSN_MAX_NUM 200000
#define RECORD_IS_REPLAY \
- (record_list->next || execution_direction == EXEC_REVERSE)
+ (record_list->next || execution_direction == EXEC_REVERSE || record_core)
+
+#define RECORD_FILE_MAGIC htonl(0x20090726)
/* These are the core struct of record function.
@@ -51,6 +59,7 @@ struct record_mem_entry
{
CORE_ADDR addr;
int len;
+ int mem_entry_not_accessible;
gdb_byte *val;
};
@@ -75,9 +84,23 @@ struct record_entry
} u;
};
+struct record_core_buf_entry
+{
+ struct record_core_buf_entry *prev;
+ struct target_section *p;
+ bfd_byte *buf;
+};
+
/* This is the debug switch for process record. */
int record_debug = 0;
+/* Record with core target. */
+static int record_core = 0;
+static gdb_byte *record_core_regbuf;
+static struct target_section *record_core_start;
+static struct target_section *record_core_end;
+static struct record_core_buf_entry *record_core_buf_list = NULL;
+
/* These list is for execution log. */
static struct record_entry record_first;
static struct record_entry *record_list = &record_first;
@@ -100,6 +123,14 @@ static struct target_ops *record_beneath
static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
struct target_waitstatus *,
int);
+static struct target_ops *record_beneath_to_fetch_registers_ops;
+static void (*record_beneath_to_fetch_registers) (struct target_ops *,
+ struct regcache *,
+ int regno);
+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_store_registers_ops;
static void (*record_beneath_to_store_registers) (struct target_ops *,
struct regcache *,
@@ -116,6 +147,9 @@ static int (*record_beneath_to_insert_br
struct bp_target_info *);
static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
struct bp_target_info *);
+static struct target_ops *record_beneath_to_has_execution_ops;
+static int (*record_beneath_to_has_execution) (struct target_ops *ops);
+static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache);
static void
record_list_release (struct record_entry *rec)
@@ -166,7 +200,7 @@ record_list_release_next (void)
}
static void
-record_list_release_first (void)
+record_list_release_first_insn (void)
{
struct record_entry *tmp = NULL;
enum record_type type;
@@ -275,6 +309,7 @@ record_arch_list_add_mem (CORE_ADDR addr
rec->type = record_mem;
rec->u.mem.addr = addr;
rec->u.mem.len = len;
+ rec->u.mem.mem_entry_not_accessible = 0;
if (target_read_memory (addr, rec->u.mem.val, len))
{
@@ -336,30 +371,30 @@ record_check_insn_num (int set_terminal)
if (q)
record_stop_at_limit = 0;
else
- error (_("Process record: inferior program stopped."));
+ error (_("Process record: stoped by user."));
}
}
}
}
+static void
+record_arch_list_cleanups (void *ignore)
+{
+ record_list_release (record_arch_list_tail);
+}
+
/* Before inferior step (when GDB record the running message, inferior
only can step), GDB will call this function to record the values to
record_list. This function will call gdbarch_process_record to
record the running message of inferior and set them to
record_arch_list, and add it to record_list. */
-static void
-record_message_cleanups (void *ignore)
-{
- record_list_release (record_arch_list_tail);
-}
-
static int
record_message (void *args)
{
int ret;
struct regcache *regcache = args;
- struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0);
+ struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
record_arch_list_head = NULL;
record_arch_list_tail = NULL;
@@ -382,7 +417,7 @@ record_message (void *args)
record_list = record_arch_list_tail;
if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
+ record_list_release_first_insn ();
else
record_insn_num++;
@@ -412,6 +447,92 @@ record_gdb_operation_disable_set (void)
return old_cleanups;
}
+static inline void
+record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch,
+ struct record_entry *entry)
+{
+ switch (entry->type)
+ {
+ case record_reg: /* reg */
+ {
+ gdb_byte reg[MAX_REGISTER_SIZE];
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_reg %s to "
+ "inferior num = %d.\n",
+ host_address_to_string (entry),
+ entry->u.reg.num);
+
+ regcache_cooked_read (regcache, entry->u.reg.num, reg);
+ regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val);
+ memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE);
+ }
+ break;
+ case record_mem: /* mem */
+ {
+ if (record_list->u.mem.mem_entry_not_accessible)
+ record_list->u.mem.mem_entry_not_accessible = 0;
+ else
+ {
+ gdb_byte *mem = alloca (entry->u.mem.len);
+
+ if (record_debug > 1)
+ fprintf_unfiltered (gdb_stdlog,
+ "Process record: record_mem %s to "
+ "inferior addr = %s len = %d.\n",
+ host_address_to_string (entry),
+ paddress (gdbarch, entry->u.mem.addr),
+ record_list->u.mem.len);
+
+ if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
+ {
+ if ((execution_direction == EXEC_REVERSE && !record_core)
+ || (execution_direction != EXEC_REVERSE && record_core))
+ {
+ record_list->u.mem.mem_entry_not_accessible = 1;
+ if (record_debug)
+ warning (_("Process record: error reading memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ else
+ error (_("Process record: error reading memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ else
+ {
+ if (target_write_memory (entry->u.mem.addr, entry->u.mem.val,
+ entry->u.mem.len))
+ {
+ if ((execution_direction == EXEC_REVERSE && !record_core)
+ || (execution_direction != EXEC_REVERSE && record_core))
+ {
+ record_list->u.mem.mem_entry_not_accessible = 1;
+ if (record_debug)
+ warning (_("Process record: error writing memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ else
+ error (_("Process record: error writing memory at "
+ "addr = %s len = %d."),
+ paddress (gdbarch, entry->u.mem.addr),
+ entry->u.mem.len);
+ }
+ }
+
+ memcpy (entry->u.mem.val, mem, entry->u.mem.len);
+ }
+ }
+ break;
+ }
+}
+
static void
record_open (char *name, int from_tty)
{
@@ -420,8 +541,13 @@ record_open (char *name, int from_tty)
if (record_debug)
fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n");
+ if (!strcmp (current_target.to_shortname, "core"))
+ record_core = 1;
+ else
+ record_core = 0;
+
/* check exec */
- if (!target_has_execution)
+ if (!target_has_execution && !record_core)
error (_("Process record: the program is not being run."));
if (non_stop)
error (_("Process record target can't debug inferior in non-stop mode "
@@ -450,6 +576,8 @@ record_open (char *name, int from_tty)
record_beneath_to_xfer_partial = NULL;
record_beneath_to_insert_breakpoint = NULL;
record_beneath_to_remove_breakpoint = NULL;
+ record_beneath_to_has_execution = NULL;
+ record_beneath_to_prepare_to_store = NULL;
/* Set the beneath function pointers. */
for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -464,6 +592,11 @@ record_open (char *name, int from_tty)
record_beneath_to_wait = t->to_wait;
record_beneath_to_wait_ops = t;
}
+ if (!record_beneath_to_fetch_registers)
+ {
+ record_beneath_to_fetch_registers = t->to_fetch_registers;
+ record_beneath_to_fetch_registers_ops = t;
+ }
if (!record_beneath_to_store_registers)
{
record_beneath_to_store_registers = t->to_store_registers;
@@ -478,19 +611,51 @@ record_open (char *name, int from_tty)
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_has_execution)
+ {
+ record_beneath_to_has_execution_ops = t;
+ record_beneath_to_has_execution = t->to_has_execution;
+ }
+ if (!record_beneath_to_prepare_to_store)
+ record_beneath_to_prepare_to_store = t->to_prepare_to_store;
}
- if (!record_beneath_to_resume)
+ if (!record_beneath_to_resume && !record_core)
error (_("Process record can't get to_resume."));
- if (!record_beneath_to_wait)
+ if (!record_beneath_to_wait && !record_core)
error (_("Process record can't get to_wait."));
- if (!record_beneath_to_store_registers)
+ if (!record_beneath_to_fetch_registers)
+ error (_("Process record can't get to_fetch_registers."));
+ if (!record_beneath_to_store_registers && !record_core)
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)
+ if (!record_beneath_to_insert_breakpoint && !record_core)
error (_("Process record can't get to_insert_breakpoint."));
- if (!record_beneath_to_remove_breakpoint)
+ if (!record_beneath_to_remove_breakpoint && !record_core)
error (_("Process record can't get to_remove_breakpoint."));
+ if (!record_beneath_to_has_execution && !record_core)
+ error (_("Process record can't get to_has_execution."));
+ if (!record_beneath_to_prepare_to_store && !record_core)
+ error (_("Process record can't get to_prepare_to_store."));
+
+ if (record_core)
+ {
+ /* Get record_core_regbuf. */
+ struct regcache *regcache = get_current_regcache ();
+ int regnum = gdbarch_num_regs (get_regcache_arch (regcache));
+ int i;
+
+ target_fetch_registers (regcache, -1);
+ record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum);
+ for (i = 0; i < regnum; i ++)
+ regcache_raw_collect (regcache, i,
+ record_core_regbuf + MAX_REGISTER_SIZE * i);
+
+ /* Get record_core_start and record_core_end. */
+ if (build_section_table (core_bfd, &record_core_start, &record_core_end))
+ error (_("\"%s\": Can't find sections: %s"),
+ bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ()));
+ }
push_target (&record_ops);
@@ -503,10 +668,26 @@ record_open (char *name, int from_tty)
static void
record_close (int quitting)
{
+ struct record_core_buf_entry *entry;
+
if (record_debug)
fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n");
record_list_release (record_list);
+
+ /* Release record_core_regbuf. */
+ xfree (record_core_regbuf);
+
+ /* Release record_core_buf_list. */
+ if (record_core_buf_list)
+ {
+ for (entry = record_core_buf_list->prev; entry; entry = entry->prev)
+ {
+ xfree (record_core_buf_list);
+ record_core_buf_list = entry;
+ }
+ record_core_buf_list = NULL;
+ }
}
static int record_resume_step = 0;
@@ -708,53 +889,9 @@ record_wait (struct target_ops *ops,
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 = %s len = %d.\n",
- host_address_to_string (record_list),
- paddress (gdbarch, 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 = %s len = %d."),
- paddress (gdbarch, 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 = %s len = %d."),
- paddress (gdbarch, record_list->u.mem.addr),
- record_list->u.mem.len);
+ record_exec_entry (regcache, gdbarch, record_list);
- memcpy (record_list->u.mem.val, mem, record_list->u.mem.len);
- }
- else
+ if (record_list->type == record_end)
{
if (record_debug > 1)
fprintf_unfiltered (gdb_stdlog,
@@ -874,7 +1011,9 @@ record_kill (struct target_ops *ops)
fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n");
unpush_target (&record_ops);
- target_kill ();
+
+ if (!record_core)
+ target_kill ();
}
/* Record registers change (by user or by GDB) to list as an instruction. */
@@ -918,15 +1057,58 @@ record_registers_change (struct regcache
record_list = record_arch_list_tail;
if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
+ record_list_release_first_insn ();
else
record_insn_num++;
}
static void
+record_fetch_registers (struct target_ops *ops, struct regcache *regcache,
+ int regno)
+{
+ if (record_core)
+ {
+ if (regno < 0)
+ {
+ int num = gdbarch_num_regs (get_regcache_arch (regcache));
+ int i;
+
+ for (i = 0; i < num; i ++)
+ regcache_raw_supply (regcache, i,
+ record_core_regbuf + MAX_REGISTER_SIZE * i);
+ }
+ else
+ regcache_raw_supply (regcache, regno,
+ record_core_regbuf + MAX_REGISTER_SIZE * regno);
+ }
+ else
+ record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops,
+ regcache, regno);
+}
+
+static void
+record_prepare_to_store (struct regcache *regcache)
+{
+ if (!record_core)
+ record_beneath_to_prepare_to_store (regcache);
+}
+
+static void
record_store_registers (struct target_ops *ops, struct regcache *regcache,
int regno)
{
+ if (record_core)
+ {
+ /* Debug with core. */
+ if (record_gdb_operation_disable)
+ regcache_raw_collect (regcache, regno,
+ record_core_regbuf + MAX_REGISTER_SIZE * regno);
+ else
+ error (_("You can't do that without a process to debug."));
+
+ return;
+ }
+
if (!record_gdb_operation_disable)
{
if (RECORD_IS_REPLAY)
@@ -973,6 +1155,7 @@ record_store_registers (struct target_op
record_registers_change (regcache, regno);
}
+
record_beneath_to_store_registers (record_beneath_to_store_registers_ops,
regcache, regno);
}
@@ -988,7 +1171,7 @@ record_xfer_partial (struct target_ops *
{
if (!record_gdb_operation_disable
&& (object == TARGET_OBJECT_MEMORY
- || object == TARGET_OBJECT_RAW_MEMORY) && writebuf)
+ || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core)
{
if (RECORD_IS_REPLAY)
{
@@ -1032,11 +1215,91 @@ record_xfer_partial (struct target_ops *
record_list = record_arch_list_tail;
if (record_insn_num == record_insn_max_num && record_insn_max_num)
- record_list_release_first ();
+ record_list_release_first_insn ();
else
record_insn_num++;
}
+ if (record_core && object == TARGET_OBJECT_MEMORY)
+ {
+ /* Debug with core. */
+ if (record_gdb_operation_disable || !writebuf)
+ {
+ struct target_section *p;
+ for (p = record_core_start; p < record_core_end; p++)
+ {
+ if (offset >= p->addr)
+ {
+ struct record_core_buf_entry *entry;
+
+ if (offset >= p->endaddr)
+ continue;
+
+ if (offset + len > p->endaddr)
+ len = p->endaddr - offset;
+
+ offset -= p->addr;
+
+ /* Read readbuf or write writebuf p, offset, len. */
+ /* Check flags. */
+ if (p->the_bfd_section->flags & SEC_CONSTRUCTOR
+ || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0)
+ {
+ if (readbuf)
+ memset (readbuf, 0, len);
+ return len;
+ }
+ /* Get record_core_buf_entry. */
+ for (entry = record_core_buf_list; entry;
+ entry = entry->prev)
+ if (entry->p == p)
+ break;
+ if (writebuf)
+ {
+ if (!entry)
+ {
+ /* Add a new entry. */
+ entry
+ = (struct record_core_buf_entry *)
+ xmalloc
+ (sizeof (struct record_core_buf_entry));
+ entry->p = p;
+ if (!bfd_malloc_and_get_section (p->bfd,
+ p->the_bfd_section,
+ &entry->buf))
+ {
+ xfree (entry);
+ return 0;
+ }
+ entry->prev = record_core_buf_list;
+ record_core_buf_list = entry;
+ }
+
+ memcpy (entry->buf + offset, writebuf, (size_t) len);
+ }
+ else
+ {
+ if (!entry)
+ return record_beneath_to_xfer_partial
+ (record_beneath_to_xfer_partial_ops,
+ object, annex, readbuf, writebuf,
+ offset, len);
+
+ memcpy (readbuf, entry->buf + offset, (size_t) len);
+ }
+
+ return len;
+ }
+ }
+
+ return 0;
+ }
+ else
+ error (_("You can't do that without a process to debug."));
+
+ return 0;
+ }
+
return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops,
object, annex, readbuf, writebuf,
offset, len);
@@ -1086,6 +1349,15 @@ record_can_execute_reverse (void)
return 1;
}
+int
+record_has_execution (struct target_ops *ops)
+{
+ if (record_core)
+ return 1;
+
+ return record_beneath_to_has_execution (ops);
+}
+
static void
init_record_ops (void)
{
@@ -1102,11 +1374,14 @@ init_record_ops (void)
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_fetch_registers = record_fetch_registers;
+ record_ops.to_prepare_to_store = record_prepare_to_store;
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_has_execution = record_has_execution;
record_ops.to_stratum = record_stratum;
record_ops.to_magic = OPS_MAGIC;
}
@@ -1127,6 +1402,293 @@ cmd_record_start (char *args, int from_t
execute_command ("target record", from_tty);
}
+static void
+cmd_record_fd_cleanups (void *recfdp)
+{
+ int recfd = *(int *) recfdp;
+ close (recfd);
+}
+
+/* Dump the execution log to a file. */
+
+static void
+cmd_record_dump (char *args, int from_tty)
+{
+ char *recfilename, recfilename_buffer[40];
+ int recfd;
+ struct record_entry *cur_record_list;
+ uint32_t magic;
+ struct regcache *regcache;
+ struct gdbarch *gdbarch;
+ struct cleanup *old_cleanups;
+ struct cleanup *set_cleanups;
+
+ if (current_target.to_stratum != record_stratum)
+ error (_("Process record is not started.\n"));
+
+ if (args && *args)
+ recfilename = args;
+ else
+ {
+ /* Default corefile name is "rec.PID". */
+ sprintf (recfilename_buffer, "rec.%d", PIDGET (inferior_ptid));
+ recfilename = recfilename_buffer;
+ }
+
+ /* Open the dump file. */
+ recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
+ S_IRUSR | S_IWUSR);
+ if (recfd < 0)
+ error (_("Failed to open '%s' for dump."), recfilename);
+ old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+ /* Save the current record entry to "cur_record_list". */
+ cur_record_list = record_list;
+
+ /* Get the values of regcache and gdbarch. */
+ regcache = get_current_regcache ();
+ gdbarch = get_regcache_arch (regcache);
+
+ /* Disable the GDB operation record. */
+ set_cleanups = record_gdb_operation_disable_set ();
+
+ /* Write the magic code. */
+ magic = RECORD_FILE_MAGIC;
+ if (write (recfd, &magic, 4) != 4)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ /* Reverse execute to the begin of record list. */
+ while (1)
+ {
+ /* Check for beginning and end of log. */
+ if (record_list == &record_first)
+ break;
+
+ record_exec_entry (regcache, gdbarch, record_list);
+
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+
+ /* Dump the entries to recfd and forward execute to the end of
+ record list. */
+ while (1)
+ {
+ /* Dump entry. */
+ if (record_list != &record_first)
+ {
+ uint8_t tmpu8;
+ uint64_t tmpu64;
+
+ tmpu8 = record_list->type;
+ if (write (recfd, &tmpu8, 1) != 1)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ switch (record_list->type)
+ {
+ case record_reg: /* reg */
+ tmpu64 = record_list->u.reg.num;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ if (write (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ if (write (recfd, record_list->u.reg.val,
+ MAX_REGISTER_SIZE) != MAX_REGISTER_SIZE)
+ error (_("Failed to write '%s' for dump."), recfilename);
+ break;
+ case record_mem: /* mem */
+ if (!record_list->u.mem.mem_entry_not_accessible)
+ {
+ tmpu64 = record_list->u.mem.addr;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ if (write (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ tmpu64 = record_list->u.mem.len;
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ if (write (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to write '%s' for dump."), recfilename);
+
+ if (write (recfd, record_list->u.mem.val,
+ record_list->u.mem.len) != record_list->u.mem.len)
+ error (_("Failed to write '%s' for dump."), recfilename);
+ }
+ break;
+ }
+ }
+
+ /* Execute entry. */
+ record_exec_entry (regcache, gdbarch, record_list);
+
+ if (record_list->next)
+ record_list = record_list->next;
+ else
+ break;
+ }
+
+ /* Reverse execute to cur_record_list. */
+ while (1)
+ {
+ /* Check for beginning and end of log. */
+ if (record_list == cur_record_list)
+ break;
+
+ record_exec_entry (regcache, gdbarch, record_list);
+
+ if (record_list->prev)
+ record_list = record_list->prev;
+ }
+
+ do_cleanups (set_cleanups);
+ do_cleanups (old_cleanups);
+
+ /* Succeeded. */
+ fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename);
+}
+
+/* Load the execution log from a file. */
+
+static void
+cmd_record_load (char *args, int from_tty)
+{
+ int recfd;
+ uint32_t magic;
+ struct cleanup *old_cleanups;
+ struct cleanup *old_cleanups2;
+ struct record_entry *rec;
+ int insn_number = 0;
+
+ if (current_target.to_stratum != record_stratum)
+ {
+ cmd_record_start (NULL, from_tty);
+ printf_unfiltered (_("Auto start process record.\n"));
+ }
+
+ if (!args || (args && !*args))
+ error (_("Argument for filename required.\n"));
+
+ /* Open the load file. */
+ recfd = open (args, O_RDONLY | O_BINARY);
+ if (recfd < 0)
+ error (_("Failed to open '%s' for load."), args);
+ old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd);
+
+ /* Check the magic code. */
+ if (read (recfd, &magic, 4) != 4)
+ error (_("Failed to read '%s' for load."), args);
+ if (magic != RECORD_FILE_MAGIC)
+ error (_("'%s' is not a record dump."), args);
+
+ /* Load the entries in recfd to the record_arch_list_head and
+ record_arch_list_tail. */
+ record_arch_list_head = NULL;
+ record_arch_list_tail = NULL;
+ old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0);
+
+ while (1)
+ {
+ int ret;
+ uint8_t tmpu8;
+ uint64_t tmpu64;
+
+ ret = read (recfd, &tmpu8, 1);
+ if (ret < 0)
+ error (_("Failed to read '%s' for load."), args);
+ if (ret == 0)
+ break;
+
+ switch (tmpu8)
+ {
+ case record_reg: /* reg */
+ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+ rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE);
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_reg;
+ /* Get num. */
+ if (read (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ rec->u.reg.num = tmpu64;
+ /* Get val. */
+ if (read (recfd, rec->u.reg.val,
+ MAX_REGISTER_SIZE) != MAX_REGISTER_SIZE)
+ error (_("Failed to read '%s' for load."), args);
+ record_arch_list_add (rec);
+ break;
+ case record_mem: /* mem */
+ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_mem;
+ /* Get addr. */
+ if (read (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ rec->u.mem.addr = tmpu64;
+ /* Get len. */
+ if (read (recfd, &tmpu64, 8) != 8)
+ error (_("Failed to read '%s' for load."), args);
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ tmpu64 = bswap_64 (tmpu64);
+#endif
+ rec->u.mem.len = tmpu64;
+ rec->u.mem.mem_entry_not_accessible = 0;
+ rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len);
+ /* Get val. */
+ if (read (recfd, rec->u.mem.val,
+ rec->u.mem.len) != rec->u.mem.len)
+ error (_("Failed to read '%s' for load."), args);
+ record_arch_list_add (rec);
+ break;
+
+ case record_end: /* end */
+ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry));
+ rec->prev = NULL;
+ rec->next = NULL;
+ rec->type = record_end;
+ record_arch_list_add (rec);
+ insn_number ++;
+ break;
+
+ default:
+ error (_("Format of '%s' is not right."), args);
+ break;
+ }
+ }
+
+ discard_cleanups (old_cleanups2);
+
+ /* Add record_arch_list_head to the end of record list. */
+ for (rec = record_list; rec->next; rec = rec->next);
+ rec->next = record_arch_list_head;
+ record_arch_list_head->prev = rec;
+
+ /* Update record_insn_num and record_insn_max_num. */
+ record_insn_num += insn_number;
+ if (record_insn_num > record_insn_max_num)
+ {
+ record_insn_max_num = record_insn_num;
+ warning (_("Auto increase record/replay buffer limit to %d."),
+ record_insn_max_num);
+ }
+
+ do_cleanups (old_cleanups);
+
+ /* Succeeded. */
+ fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args);
+}
+
/* Truncate the record log from the present point
of replay until the end. */
@@ -1177,7 +1739,7 @@ set_record_insn_max_num (char *args, int
"the first ones?\n"));
while (record_insn_num > record_insn_max_num)
- record_list_release_first ();
+ record_list_release_first_insn ();
}
}
@@ -1217,6 +1779,8 @@ info_record_command (char *args, int fro
void
_initialize_record (void)
{
+ struct cmd_list_element *c;
+
/* Init record_first. */
record_first.prev = NULL;
record_first.next = NULL;
@@ -1250,6 +1814,15 @@ _initialize_record (void)
"info record ", 0, &infolist);
add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
+ c = add_cmd ("dump", class_obscure, cmd_record_dump,
+ _("Dump the execution log to a file.\n\
+Argument is optional filename. Default filename is 'rec.<process_id>'."),
+ &record_cmdlist);
+ set_cmd_completer (c, filename_completer);
+ c = add_cmd ("load", class_obscure, cmd_record_load,
+ _("Load the execution log from a file. Argument is filename."),
+ &record_cmdlist);
+ set_cmd_completer (c, filename_completer);
add_cmd ("delete", class_obscure, cmd_record_delete,
_("Delete the rest of execution log and start recording it anew."),
[-- Attachment #3: prec-dump-doc.txt --]
[-- Type: text/plain, Size: 957 bytes --]
---
doc/gdb.texinfo | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
--- a/doc/gdb.texinfo
+++ b/doc/gdb.texinfo
@@ -5190,6 +5190,22 @@ When record target runs in replay mode (
subsequent execution log and begin to record a new execution log starting
from the current address. This means you will abandon the previously
recorded ``future'' and begin recording a new ``future''.
+
+@kindex record dump
+@kindex rec dump
+@item record dump [@var{file}]
+@itemx rec dump [@var{file}]
+Produce a record execution log of the inferior process. The optional
+argument @var{file} specifies the file name where to put the record dump.
+If not specified, the file name defaults to @file{rec.@var{pid}}, where
+@var{pid} is the inferior process ID.
+
+@kindex record load
+@kindex rec load
+@item record load @var{file}
+@itemx rec dump @var{file}
+Load process record execution log from @var{file}.
+It can work with @code{core-file}.
@end table
^ permalink raw reply [flat|nested] 51+ messages in thread* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-01 7:31 Hui Zhu @ 2009-08-01 9:57 ` Eli Zaretskii 2009-08-01 19:20 ` Michael Snyder 1 sibling, 0 replies; 51+ messages in thread From: Eli Zaretskii @ 2009-08-01 9:57 UTC (permalink / raw) To: Hui Zhu; +Cc: gdb-patches, msnyder > From: Hui Zhu <teawater@gmail.com> > Date: Sat, 1 Aug 2009 15:30:38 +0800 > Cc: Michael Snyder <msnyder@vmware.com>, Eli Zaretskii <eliz@gnu.org> > > This patch add command record dump and record load to make prec can > dump execution log to a file and load it later. And load can work > with core file. Thanks. > + /* Default corefile name is "rec.PID". */ I suggest the default name to be "gdb_record.PID". > + if (write (recfd, &magic, 4) != 4) > + error (_("Failed to write '%s' for dump."), recfilename); I suggest to change the text (here and elsewhere in your patch) to: "Failed to write dump of execution records to `%s'." I also suggest to replace the numerous fragments like this: > + if (write (recfd, &tmpu8, 1) != 1) > + error (_("Failed to write '%s' for dump."), recfilename); with calls to a function which tries to write and errors out of it cannot. That would leave only one "Failed to write ..." string, instead of having gobs of them. > + fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename); I suggest to replace the message text with this: "Saved dump of execution records to `%s'." > + if (recfd < 0) > + error (_("Failed to open '%s' for load."), args); A better text would be "Failed to open `%s' for loading execution records: %s" The second %s should get the result of strerror (errno), to describe the reason of the failure > + if (read (recfd, &magic, 4) != 4) > + error (_("Failed to read '%s' for load."), args); "Failed reading dump of execution records in `%s'" Again, I suggest to have a single function that reads and errors out on error. > + if (magic != RECORD_FILE_MAGIC) > + error (_("'%s' is not a record dump."), args); "`%s' is not a valid dump of execution records." > + c = add_cmd ("dump", class_obscure, cmd_record_dump, > + _("Dump the execution log to a file.\n\ "Dump the execution records to a file." > +Argument is optional filename. Default filename is 'rec.<process_id>'."), > + &record_cmdlist); > + set_cmd_completer (c, filename_completer); > + c = add_cmd ("load", class_obscure, cmd_record_load, > + _("Load the execution log from a file. Argument is filename."), "Load previously dumped execution records from a file given as argument." Note that you cannot have more than one period on the first line, because the help commands assume there's only one there. > +@kindex record dump > +@kindex rec dump > +@item record dump [@var{file}] > +@itemx rec dump [@var{file}] > +Produce a record execution log of the inferior process. The optional ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Dump the execution records of the inferior process to a file. > +argument @var{file} specifies the file name where to put the record dump. > +If not specified, the file name defaults to @file{rec.@var{pid}}, where > +@var{pid} is the inferior process ID. ^^^^^^^^^^^^^^^^^^^^^^^^^^ "is the PID of the inferior process." > +Load process record execution log from @var{file}. Load previously-dumped execution records from @var{file}. > +It can work with @code{core-file}. This needs further explanation. Or maybe just delete it, if it silently does The Right Thing. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-01 7:31 Hui Zhu 2009-08-01 9:57 ` Eli Zaretskii @ 2009-08-01 19:20 ` Michael Snyder 2009-08-02 3:18 ` Michael Snyder 1 sibling, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-01 19:20 UTC (permalink / raw) To: Hui Zhu; +Cc: gdb-patches ml, Eli Zaretskii Hui Zhu wrote: > Hi, > > This patch add command record dump and record load to make prec can > dump execution log to a file and load it later. And load can work > with core file. > > And I include "mem_entry_not_accessible"(not_replay) patch to this > patch because when replay with core file, some address in lib cannot > be read and write. Augh! No -- please, this is lumping too much together. Smaller, more discrete patches are much easier to review. 1) You already have a patch thread for the memory-not-accessible change -- let's finish that idea there, not lump it in here. 2) I think the idea of saving and restoring the recording to a file is GREAT -- but can you please give us a patch that adds that, by itself. 3) I don't really understand how core files fit into this, but I'd love to discuss that idea in a separate patch thread. It really adds a lot of complexity to this patch, which it seems to me could be much simpler and easier to review if it added only one thing at a time. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-01 19:20 ` Michael Snyder @ 2009-08-02 3:18 ` Michael Snyder 2009-08-02 5:58 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-02 3:18 UTC (permalink / raw) To: Michael Snyder; +Cc: Hui Zhu, gdb-patches ml Michael Snyder wrote: > 3) I don't really understand how core files fit into this, > but I'd love to discuss that idea in a separate patch thread. Oh, sorry -- I see how they're related now. Very clever, actually. I'd still like to see them submitted separately, though, because: 1) the dump/restore part of the patch is cleaner and closer to being acceptable, and can really be used on its own, while 2) the corefile part of the patch is kind of messy and prototype-ish, and I think needs much more discussion and cleaning up before it will be ready. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-02 3:18 ` Michael Snyder @ 2009-08-02 5:58 ` Hui Zhu 2009-08-03 4:12 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-02 5:58 UTC (permalink / raw) To: Michael Snyder; +Cc: gdb-patches ml OK. I will post new patches for them, memory and dump. Thanks, Hui On Sun, Aug 2, 2009 at 11:17, Michael Snyder<msnyder@vmware.com> wrote: > Michael Snyder wrote: > >> 3) I don't really understand how core files fit into this, >> but I'd love to discuss that idea in a separate patch thread. > > Oh, sorry -- I see how they're related now. > Very clever, actually. > > I'd still like to see them submitted separately, though, > because: > 1) the dump/restore part of the patch is cleaner and > closer to being acceptable, and can really be used on > its own, while > 2) the corefile part of the patch is kind of messy and > prototype-ish, and I think needs much more discussion and > cleaning up before it will be ready. > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-02 5:58 ` Hui Zhu @ 2009-08-03 4:12 ` Hui Zhu 2009-08-03 18:29 ` Eli Zaretskii 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-03 4:12 UTC (permalink / raw) To: Michael Snyder, Eli Zaretskii; +Cc: gdb-patches ml [-- Attachment #1: Type: text/plain, Size: 32404 bytes --] On Sun, Aug 2, 2009 at 13:58, Hui Zhu<teawater@gmail.com> wrote: > OK. I will post new patches for them, memory and dump. > > Thanks, > Hui > > On Sun, Aug 2, 2009 at 11:17, Michael Snyder<msnyder@vmware.com> wrote: >> Michael Snyder wrote: >> >>> 3) I don't really understand how core files fit into this, >>> but I'd love to discuss that idea in a separate patch thread. >> >> Oh, sorry -- I see how they're related now. >> Very clever, actually. >> >> I'd still like to see them submitted separately, though, >> because: >> 1) the dump/restore part of the patch is cleaner and >> closer to being acceptable, and can really be used on >> its own, while >> 2) the corefile part of the patch is kind of messy and >> prototype-ish, and I think needs much more discussion and >> cleaning up before it will be ready. >> > Hi Eli and Michael, I make a new patch according to your mail. It depend on patch in http://sourceware.org/ml/gdb-patches/2009-08/msg00015.html Please help me review it. Thanks, Hui 2009-08-03 Hui Zhu <teawater@gmail.com> Add dump and load command to process record and replay. * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h, byteswap.h, netinet/in.h): Include files. (RECORD_IS_REPLAY): Return true if record_core is true. (RECORD_FILE_MAGIC): New macro. (record_core_buf_entry): New struct. (record_core, record_core_regbuf, record_core_start, record_core_end, record_core_buf_list, record_beneath_to_fetch_registers_ops, record_beneath_to_fetch_registers, record_beneath_to_store_registers_ops, record_beneath_to_store_registers, record_beneath_to_has_execution_ops, record_beneath_to_has_execution, record_beneath_to_prepare_to_store): New variables. (record_list_release_first_insn): Change function record_list_release_first to this name. (record_arch_list_cleanups): New function. (record_message_cleanups): Removed. (record_message): Change to call record_arch_list_cleanups and record_list_release_first_insn. (record_exec_entry): Add support for target core. (record_open): Add support for target core. (record_close): Add support for target core. (record_kill): Add support for target core. (record_registers_change): Call record_list_release_first_insn. (record_fetch_registers): New function. (record_prepare_to_store): New function. (record_store_registers): Add support for target core. (record_xfer_partial): Add support for target core. (record_has_execution): New function. (init_record_ops): Set record_ops.to_fetch_registers, record_ops.to_prepare_to_store and record_ops.to_has_execution. (cmd_record_fd_cleanups): New function. (cmd_record_dump): New function. (cmd_record_load): New function. (set_record_insn_max_num): Call record_list_release_first_insn. (_initialize_record): Add commands "record dump" and "record load". --- record.c | 587 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 563 insertions(+), 24 deletions(-) --- a/record.c +++ b/record.c @@ -23,14 +23,22 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) + (record_list->next || execution_direction == EXEC_REVERSE || record_core) + +#define RECORD_FILE_MAGIC htonl(0x20090726) /* These are the core struct of record function. @@ -76,9 +84,23 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record with core target. */ +static int record_core = 0; +static gdb_byte *record_core_regbuf; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -101,6 +123,14 @@ static struct target_ops *record_beneath static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, struct target_waitstatus *, int); +static struct target_ops *record_beneath_to_fetch_registers_ops; +static void (*record_beneath_to_fetch_registers) (struct target_ops *, + struct regcache *, + int regno); +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_store_registers_ops; static void (*record_beneath_to_store_registers) (struct target_ops *, struct regcache *, @@ -117,6 +147,9 @@ static int (*record_beneath_to_insert_br struct bp_target_info *); static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); +static struct target_ops *record_beneath_to_has_execution_ops; +static int (*record_beneath_to_has_execution) (struct target_ops *ops); +static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache); static void record_list_release (struct record_entry *rec) @@ -167,7 +200,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -338,30 +371,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -384,7 +417,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -453,7 +486,8 @@ record_exec_entry (struct regcache *regc if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) { - if (execution_direction == EXEC_REVERSE) + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) @@ -473,7 +507,8 @@ record_exec_entry (struct regcache *regc if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, entry->u.mem.len)) { - if (execution_direction == EXEC_REVERSE) + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) @@ -505,8 +540,13 @@ record_open (char *name, int from_tty) if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + if (!strcmp (current_target.to_shortname, "core")) + record_core = 1; + else + record_core = 0; + /* check exec */ - if (!target_has_execution) + if (!target_has_execution && !record_core) error (_("Process record: the program is not being run.")); if (non_stop) error (_("Process record target can't debug inferior in non-stop mode " @@ -535,6 +575,8 @@ record_open (char *name, int from_tty) record_beneath_to_xfer_partial = NULL; record_beneath_to_insert_breakpoint = NULL; record_beneath_to_remove_breakpoint = NULL; + record_beneath_to_has_execution = NULL; + record_beneath_to_prepare_to_store = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) @@ -549,6 +591,11 @@ record_open (char *name, int from_tty) record_beneath_to_wait = t->to_wait; record_beneath_to_wait_ops = t; } + if (!record_beneath_to_fetch_registers) + { + record_beneath_to_fetch_registers = t->to_fetch_registers; + record_beneath_to_fetch_registers_ops = t; + } if (!record_beneath_to_store_registers) { record_beneath_to_store_registers = t->to_store_registers; @@ -563,19 +610,51 @@ record_open (char *name, int from_tty) 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_has_execution) + { + record_beneath_to_has_execution_ops = t; + record_beneath_to_has_execution = t->to_has_execution; + } + if (!record_beneath_to_prepare_to_store) + record_beneath_to_prepare_to_store = t->to_prepare_to_store; } - if (!record_beneath_to_resume) + if (!record_beneath_to_resume && !record_core) error (_("Process record can't get to_resume.")); - if (!record_beneath_to_wait) + if (!record_beneath_to_wait && !record_core) error (_("Process record can't get to_wait.")); - if (!record_beneath_to_store_registers) + if (!record_beneath_to_fetch_registers) + error (_("Process record can't get to_fetch_registers.")); + if (!record_beneath_to_store_registers && !record_core) 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) + if (!record_beneath_to_insert_breakpoint && !record_core) error (_("Process record can't get to_insert_breakpoint.")); - if (!record_beneath_to_remove_breakpoint) + if (!record_beneath_to_remove_breakpoint && !record_core) error (_("Process record can't get to_remove_breakpoint.")); + if (!record_beneath_to_has_execution && !record_core) + error (_("Process record can't get to_has_execution.")); + if (!record_beneath_to_prepare_to_store && !record_core) + error (_("Process record can't get to_prepare_to_store.")); + + if (record_core) + { + /* Get record_core_regbuf. */ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } push_target (&record_ops); @@ -588,10 +667,26 @@ record_open (char *name, int from_tty) static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + xfree (record_core_regbuf); + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -915,7 +1010,9 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); - target_kill (); + + if (!record_core) + target_kill (); } /* Record registers change (by user or by GDB) to list as an instruction. */ @@ -959,15 +1056,58 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } static void +record_fetch_registers (struct target_ops *ops, struct regcache *regcache, + int regno) +{ + if (record_core) + { + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + } + else + record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops, + regcache, regno); +} + +static void +record_prepare_to_store (struct regcache *regcache) +{ + if (!record_core) + record_beneath_to_prepare_to_store (regcache); +} + +static void record_store_registers (struct target_ops *ops, struct regcache *regcache, int regno) { + if (record_core) + { + /* Debug with core. */ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); + + return; + } + if (!record_gdb_operation_disable) { if (RECORD_IS_REPLAY) @@ -1014,6 +1154,7 @@ record_store_registers (struct target_op record_registers_change (regcache, regno); } + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, regcache, regno); } @@ -1029,7 +1170,7 @@ record_xfer_partial (struct target_ops * { if (!record_gdb_operation_disable && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core) { if (RECORD_IS_REPLAY) { @@ -1073,11 +1214,91 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } + if (record_core && object == TARGET_OBJECT_MEMORY) + { + /* Debug with core. */ + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return 0; + } + else + error (_("You can't do that without a process to debug.")); + + return 0; + } + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); @@ -1127,6 +1348,15 @@ record_can_execute_reverse (void) return 1; } +int +record_has_execution (struct target_ops *ops) +{ + if (record_core) + return 1; + + return record_beneath_to_has_execution (ops); +} + static void init_record_ops (void) { @@ -1143,11 +1373,14 @@ init_record_ops (void) 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_fetch_registers = record_fetch_registers; + record_ops.to_prepare_to_store = record_prepare_to_store; 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_has_execution = record_has_execution; record_ops.to_stratum = record_stratum; record_ops.to_magic = OPS_MAGIC; } @@ -1168,6 +1401,300 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_fd_cleanups (void *recfdp) +{ + int recfd = *(int *) recfdp; + close (recfd); +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (current_target.to_stratum != record_stratum) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + tmpu64 = record_list->u.reg.num; +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + tmpu64 = record_list->u.mem.addr; +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + +/* Load the execution log from a file. */ + +static void +cmd_record_load (char *args, int from_tty) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (current_target.to_stratum != record_stratum) + { + cmd_record_start (NULL, from_tty); + printf_unfiltered (_("Auto start process record.\n")); + } + + if (!args || (args && !*args)) + error (_("Argument for filename required.\n")); + + /* Open the load file. */ + recfd = open (args, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + args, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (args, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), args); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), args); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + /* Get num. */ + record_read_dump (args, recfd, &tmpu64, 8); +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + rec->u.reg.num = tmpu64; + /* Get val. */ + record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + record_arch_list_add (rec); + break; + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + /* Get addr. */ + record_read_dump (args, recfd, &tmpu64, 8); +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + rec->u.mem.addr = tmpu64; + /* Get len. */ + record_read_dump (args, recfd, &tmpu64, 8); +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + /* Get val. */ + record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len); + record_arch_list_add (rec); + break; + + case record_end: /* end */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), args); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1218,7 +1745,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1258,6 +1785,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1291,6 +1820,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), 2009-08-03 Hui Zhu <teawater@gmail.com> * gdb.texinfo (Process Record and Replay): Document the "record dump" and "record dump" commands. --- doc/gdb.texinfo | 15 +++++++++++++++ 1 file changed, 15 insertions(+) --- a/doc/gdb.texinfo +++ b/doc/gdb.texinfo @@ -5190,6 +5190,21 @@ When record target runs in replay mode ( subsequent execution log and begin to record a new execution log starting from the current address. This means you will abandon the previously recorded ``future'' and begin recording a new ``future''. + +@kindex record dump +@kindex rec dump +@item record dump [@var{file}] +@itemx rec dump [@var{file}] +Dump the execution records of the inferior process to a file. The optional +argument @var{file} specifies the file name where to put the record dump. +If not specified, the file name defaults to @file{gdb_record.@var{pid}}, where +@var{pid} is is the PID of the inferior process. + +@kindex record load +@kindex rec load +@item record load @var{file} +@itemx rec dump @var{file} +Load previously-dumped execution records from @var{file}. @end table [-- Attachment #2: prec-dump.txt --] [-- Type: text/plain, Size: 27569 bytes --] --- record.c | 587 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 563 insertions(+), 24 deletions(-) --- a/record.c +++ b/record.c @@ -23,14 +23,22 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) + (record_list->next || execution_direction == EXEC_REVERSE || record_core) + +#define RECORD_FILE_MAGIC htonl(0x20090726) /* These are the core struct of record function. @@ -76,9 +84,23 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record with core target. */ +static int record_core = 0; +static gdb_byte *record_core_regbuf; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -101,6 +123,14 @@ static struct target_ops *record_beneath static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, struct target_waitstatus *, int); +static struct target_ops *record_beneath_to_fetch_registers_ops; +static void (*record_beneath_to_fetch_registers) (struct target_ops *, + struct regcache *, + int regno); +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_store_registers_ops; static void (*record_beneath_to_store_registers) (struct target_ops *, struct regcache *, @@ -117,6 +147,9 @@ static int (*record_beneath_to_insert_br struct bp_target_info *); static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); +static struct target_ops *record_beneath_to_has_execution_ops; +static int (*record_beneath_to_has_execution) (struct target_ops *ops); +static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache); static void record_list_release (struct record_entry *rec) @@ -167,7 +200,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -338,30 +371,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -384,7 +417,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -453,7 +486,8 @@ record_exec_entry (struct regcache *regc if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) { - if (execution_direction == EXEC_REVERSE) + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) @@ -473,7 +507,8 @@ record_exec_entry (struct regcache *regc if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, entry->u.mem.len)) { - if (execution_direction == EXEC_REVERSE) + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) @@ -505,8 +540,13 @@ record_open (char *name, int from_tty) if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + if (!strcmp (current_target.to_shortname, "core")) + record_core = 1; + else + record_core = 0; + /* check exec */ - if (!target_has_execution) + if (!target_has_execution && !record_core) error (_("Process record: the program is not being run.")); if (non_stop) error (_("Process record target can't debug inferior in non-stop mode " @@ -535,6 +575,8 @@ record_open (char *name, int from_tty) record_beneath_to_xfer_partial = NULL; record_beneath_to_insert_breakpoint = NULL; record_beneath_to_remove_breakpoint = NULL; + record_beneath_to_has_execution = NULL; + record_beneath_to_prepare_to_store = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) @@ -549,6 +591,11 @@ record_open (char *name, int from_tty) record_beneath_to_wait = t->to_wait; record_beneath_to_wait_ops = t; } + if (!record_beneath_to_fetch_registers) + { + record_beneath_to_fetch_registers = t->to_fetch_registers; + record_beneath_to_fetch_registers_ops = t; + } if (!record_beneath_to_store_registers) { record_beneath_to_store_registers = t->to_store_registers; @@ -563,19 +610,51 @@ record_open (char *name, int from_tty) 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_has_execution) + { + record_beneath_to_has_execution_ops = t; + record_beneath_to_has_execution = t->to_has_execution; + } + if (!record_beneath_to_prepare_to_store) + record_beneath_to_prepare_to_store = t->to_prepare_to_store; } - if (!record_beneath_to_resume) + if (!record_beneath_to_resume && !record_core) error (_("Process record can't get to_resume.")); - if (!record_beneath_to_wait) + if (!record_beneath_to_wait && !record_core) error (_("Process record can't get to_wait.")); - if (!record_beneath_to_store_registers) + if (!record_beneath_to_fetch_registers) + error (_("Process record can't get to_fetch_registers.")); + if (!record_beneath_to_store_registers && !record_core) 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) + if (!record_beneath_to_insert_breakpoint && !record_core) error (_("Process record can't get to_insert_breakpoint.")); - if (!record_beneath_to_remove_breakpoint) + if (!record_beneath_to_remove_breakpoint && !record_core) error (_("Process record can't get to_remove_breakpoint.")); + if (!record_beneath_to_has_execution && !record_core) + error (_("Process record can't get to_has_execution.")); + if (!record_beneath_to_prepare_to_store && !record_core) + error (_("Process record can't get to_prepare_to_store.")); + + if (record_core) + { + /* Get record_core_regbuf. */ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } push_target (&record_ops); @@ -588,10 +667,26 @@ record_open (char *name, int from_tty) static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + xfree (record_core_regbuf); + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -915,7 +1010,9 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); - target_kill (); + + if (!record_core) + target_kill (); } /* Record registers change (by user or by GDB) to list as an instruction. */ @@ -959,15 +1056,58 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } static void +record_fetch_registers (struct target_ops *ops, struct regcache *regcache, + int regno) +{ + if (record_core) + { + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + } + else + record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops, + regcache, regno); +} + +static void +record_prepare_to_store (struct regcache *regcache) +{ + if (!record_core) + record_beneath_to_prepare_to_store (regcache); +} + +static void record_store_registers (struct target_ops *ops, struct regcache *regcache, int regno) { + if (record_core) + { + /* Debug with core. */ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); + + return; + } + if (!record_gdb_operation_disable) { if (RECORD_IS_REPLAY) @@ -1014,6 +1154,7 @@ record_store_registers (struct target_op record_registers_change (regcache, regno); } + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, regcache, regno); } @@ -1029,7 +1170,7 @@ record_xfer_partial (struct target_ops * { if (!record_gdb_operation_disable && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core) { if (RECORD_IS_REPLAY) { @@ -1073,11 +1214,91 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } + if (record_core && object == TARGET_OBJECT_MEMORY) + { + /* Debug with core. */ + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return 0; + } + else + error (_("You can't do that without a process to debug.")); + + return 0; + } + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); @@ -1127,6 +1348,15 @@ record_can_execute_reverse (void) return 1; } +int +record_has_execution (struct target_ops *ops) +{ + if (record_core) + return 1; + + return record_beneath_to_has_execution (ops); +} + static void init_record_ops (void) { @@ -1143,11 +1373,14 @@ init_record_ops (void) 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_fetch_registers = record_fetch_registers; + record_ops.to_prepare_to_store = record_prepare_to_store; 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_has_execution = record_has_execution; record_ops.to_stratum = record_stratum; record_ops.to_magic = OPS_MAGIC; } @@ -1168,6 +1401,300 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_fd_cleanups (void *recfdp) +{ + int recfd = *(int *) recfdp; + close (recfd); +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (current_target.to_stratum != record_stratum) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + tmpu64 = record_list->u.reg.num; +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + tmpu64 = record_list->u.mem.addr; +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + +/* Load the execution log from a file. */ + +static void +cmd_record_load (char *args, int from_tty) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (current_target.to_stratum != record_stratum) + { + cmd_record_start (NULL, from_tty); + printf_unfiltered (_("Auto start process record.\n")); + } + + if (!args || (args && !*args)) + error (_("Argument for filename required.\n")); + + /* Open the load file. */ + recfd = open (args, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + args, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (args, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), args); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), args); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + /* Get num. */ + record_read_dump (args, recfd, &tmpu64, 8); +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + rec->u.reg.num = tmpu64; + /* Get val. */ + record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + record_arch_list_add (rec); + break; + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + /* Get addr. */ + record_read_dump (args, recfd, &tmpu64, 8); +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + rec->u.mem.addr = tmpu64; + /* Get len. */ + record_read_dump (args, recfd, &tmpu64, 8); +#if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); +#endif + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + /* Get val. */ + record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len); + record_arch_list_add (rec); + break; + + case record_end: /* end */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), args); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1218,7 +1745,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1258,6 +1785,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1291,6 +1820,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), [-- Attachment #3: prec-dump-doc.txt --] [-- Type: text/plain, Size: 951 bytes --] --- doc/gdb.texinfo | 15 +++++++++++++++ 1 file changed, 15 insertions(+) --- a/doc/gdb.texinfo +++ b/doc/gdb.texinfo @@ -5190,6 +5190,21 @@ When record target runs in replay mode ( subsequent execution log and begin to record a new execution log starting from the current address. This means you will abandon the previously recorded ``future'' and begin recording a new ``future''. + +@kindex record dump +@kindex rec dump +@item record dump [@var{file}] +@itemx rec dump [@var{file}] +Dump the execution records of the inferior process to a file. The optional +argument @var{file} specifies the file name where to put the record dump. +If not specified, the file name defaults to @file{gdb_record.@var{pid}}, where +@var{pid} is is the PID of the inferior process. + +@kindex record load +@kindex rec load +@item record load @var{file} +@itemx rec dump @var{file} +Load previously-dumped execution records from @var{file}. @end table ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-03 4:12 ` Hui Zhu @ 2009-08-03 18:29 ` Eli Zaretskii 2009-08-04 1:58 ` Hui Zhu 2009-08-04 2:07 ` Hui Zhu 0 siblings, 2 replies; 51+ messages in thread From: Eli Zaretskii @ 2009-08-03 18:29 UTC (permalink / raw) To: Hui Zhu; +Cc: msnyder, gdb-patches > From: Hui Zhu <teawater@gmail.com> > Date: Mon, 3 Aug 2009 12:11:46 +0800 > Cc: gdb-patches ml <gdb-patches@sourceware.org> > > I make a new patch according to your mail. It depend on patch in > http://sourceware.org/ml/gdb-patches/2009-08/msg00015.html Thanks. > +@var{pid} is is the PID of the inferior process. ^^^^^ Two "is" in a row; one is redundant. Otherwise, I'm happy now. By the way, does it make sense to make the dump file portable, so that another host that supports debugging the same target could then use it? ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-03 18:29 ` Eli Zaretskii @ 2009-08-04 1:58 ` Hui Zhu 2009-08-04 2:07 ` Hui Zhu 1 sibling, 0 replies; 51+ messages in thread From: Hui Zhu @ 2009-08-04 1:58 UTC (permalink / raw) To: Eli Zaretskii; +Cc: msnyder, gdb-patches On Tue, Aug 4, 2009 at 02:29, Eli Zaretskii<eliz@gnu.org> wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Mon, 3 Aug 2009 12:11:46 +0800 >> Cc: gdb-patches ml <gdb-patches@sourceware.org> >> >> I make a new patch according to your mail. It depend on patch in >> http://sourceware.org/ml/gdb-patches/2009-08/msg00015.html > > Thanks. > >> +@var{pid} is is the PID of the inferior process. > ^^^^^ > Two "is" in a row; one is redundant. > > Otherwise, I'm happy now. > > By the way, does it make sense to make the dump file portable, so that > another host that supports debugging the same target could then use > it? > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-03 18:29 ` Eli Zaretskii 2009-08-04 1:58 ` Hui Zhu @ 2009-08-04 2:07 ` Hui Zhu 2009-08-04 18:26 ` Eli Zaretskii 1 sibling, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-04 2:07 UTC (permalink / raw) To: Eli Zaretskii; +Cc: msnyder, gdb-patches On Tue, Aug 4, 2009 at 02:29, Eli Zaretskii<eliz@gnu.org> wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Mon, 3 Aug 2009 12:11:46 +0800 >> Cc: gdb-patches ml <gdb-patches@sourceware.org> >> >> I make a new patch according to your mail. It depend on patch in >> http://sourceware.org/ml/gdb-patches/2009-08/msg00015.html > > Thanks. > >> +@var{pid} is is the PID of the inferior process. > ^^^^^ > Two "is" in a row; one is redundant. Oops, I will fix it. > > Otherwise, I'm happy now. > > By the way, does it make sense to make the dump file portable, so that > another host that supports debugging the same target could then use > it? > Yes, the dump file is portable like core file. Thanks, Hui ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-04 2:07 ` Hui Zhu @ 2009-08-04 18:26 ` Eli Zaretskii 2009-08-04 20:01 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Eli Zaretskii @ 2009-08-04 18:26 UTC (permalink / raw) To: Hui Zhu; +Cc: msnyder, gdb-patches > From: Hui Zhu <teawater@gmail.com> > Date: Tue, 4 Aug 2009 10:07:11 +0800 > Cc: msnyder@vmware.com, gdb-patches@sourceware.org > > > By the way, does it make sense to make the dump file portable, so that > > another host that supports debugging the same target could then use > > it? > > > > Yes, the dump file is portable like core file. Well, maybe I'm missing something important, but isn't the dump file a series of struct's defined as this: struct record_entry { struct record_entry *prev; struct record_entry *next; enum record_type type; union { /* reg */ struct record_reg_entry reg; /* mem */ struct record_mem_entry mem; } u; }; ? If so, then the dump file uses host's native pointers, so it is not portable to a different host. Right? ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-04 18:26 ` Eli Zaretskii @ 2009-08-04 20:01 ` Michael Snyder 2009-08-05 9:21 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-04 20:01 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Hui Zhu, gdb-patches Eli Zaretskii wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Tue, 4 Aug 2009 10:07:11 +0800 >> Cc: msnyder@vmware.com, gdb-patches@sourceware.org >> >>> By the way, does it make sense to make the dump file portable, so that >>> another host that supports debugging the same target could then use >>> it? >>> >> Yes, the dump file is portable like core file. > > Well, maybe I'm missing something important, but isn't the dump file a > series of struct's defined as this: > > struct record_entry > { > struct record_entry *prev; > struct record_entry *next; > enum record_type type; > union > { > /* reg */ > struct record_reg_entry reg; > /* mem */ > struct record_mem_entry mem; > } u; > }; > > ? If so, then the dump file uses host's native pointers, so it is not > portable to a different host. Right? No. If you look at the code that dumps the file (and I had to add a bunch of printfs and stuff to figure it out), you'll see that the dump file looks like this: 4 byte magic number Series of the following: 1) 1 byte tag (record_end), or 2) 1 byte tag (record_rec) followed by 8 byte number (register id), followed by MAX_REGISTER_SIZE byte value (register value): or 3) 1 byte tag (record_mem) followed by 8 byte number (memory address) followed by 8 byte number (memory length) followed by N byte buffer (memory value). If you look for the #if (BYTE_ORDER == BIG_ENDIAN), this is where Teawater is making the byte orders host-independent. I was going to mention the ifdefs eventually. ;-) ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-04 20:01 ` Michael Snyder @ 2009-08-05 9:21 ` Hui Zhu 2009-08-05 21:23 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-05 9:21 UTC (permalink / raw) To: Michael Snyder, Eli Zaretskii; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 37788 bytes --] On Wed, Aug 5, 2009 at 04:00, Michael Snyder<msnyder@vmware.com> wrote: > Eli Zaretskii wrote: >>> >>> From: Hui Zhu <teawater@gmail.com> >>> Date: Tue, 4 Aug 2009 10:07:11 +0800 >>> Cc: msnyder@vmware.com, gdb-patches@sourceware.org >>> >>>> By the way, does it make sense to make the dump file portable, so that >>>> another host that supports debugging the same target could then use >>>> it? >>>> >>> Yes, the dump file is portable like core file. >> >> Well, maybe I'm missing something important, but isn't the dump file a >> series of struct's defined as this: >> >> struct record_entry >> { >> struct record_entry *prev; >> struct record_entry *next; >> enum record_type type; >> union >> { >> /* reg */ >> struct record_reg_entry reg; >> /* mem */ >> struct record_mem_entry mem; >> } u; >> }; >> >> ? If so, then the dump file uses host's native pointers, so it is not >> portable to a different host. Right? > > No. If you look at the code that dumps the file (and I had > to add a bunch of printfs and stuff to figure it out), you'll > see that the dump file looks like this: > > 4 byte magic number > Series of the following: > 1) 1 byte tag (record_end), or > 2) 1 byte tag (record_rec) followed by > 8 byte number (register id), followed by > MAX_REGISTER_SIZE byte value (register value): or > 3) 1 byte tag (record_mem) followed by > 8 byte number (memory address) followed by > 8 byte number (memory length) followed by > N byte buffer (memory value). > > If you look for the #if (BYTE_ORDER == BIG_ENDIAN), this is > where Teawater is making the byte orders host-independent. > > I was going to mention the ifdefs eventually. ;-) > Cool. It's very clear. :) Thanks Michael. I make a new version patch to follow the cvs head. And removed the ifdef. Please help me review it. Thanks, Hui 2009-08-05 Hui Zhu <teawater@gmail.com> Add dump and load command to process record and replay. * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h, byteswap.h, netinet/in.h): Include files. (RECORD_IS_REPLAY): Return true if record_core is true. (RECORD_FILE_MAGIC): New macro. (record_core_buf_entry): New struct. (record_core, record_core_regbuf, record_core_start, record_core_end, record_core_buf_list, record_beneath_to_fetch_registers_ops, record_beneath_to_fetch_registers, record_beneath_to_store_registers_ops, record_beneath_to_store_registers, record_beneath_to_has_execution_ops, record_beneath_to_has_execution, record_beneath_to_prepare_to_store): New variables. (record_list_release_first_insn): Change function record_list_release_first to this name. (record_arch_list_cleanups): New function. (record_message_cleanups): Removed. (record_message): Change to call record_arch_list_cleanups and record_list_release_first_insn. (record_exec_entry): New function. (record_open): Add support for target core. (record_close): Add support for target core. (record_wait): Call function 'record_exec_entry'. (record_kill): Add support for target core. (record_registers_change): Call record_list_release_first_insn. (record_fetch_registers): New function. (record_prepare_to_store): New function. (record_store_registers): Add support for target core. (record_xfer_partial): Add support for target core. (record_has_execution): New function. (init_record_ops): Set record_ops.to_fetch_registers, record_ops.to_prepare_to_store and record_ops.to_has_execution. (cmd_record_fd_cleanups): New function. (cmd_record_dump): New function. (cmd_record_load): New function. (set_record_insn_max_num): Call record_list_release_first_insn. (_initialize_record): Add commands "record dump" and "record load". --- record.c | 731 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 640 insertions(+), 91 deletions(-) --- a/record.c +++ b/record.c @@ -23,14 +23,22 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) + (record_list->next || execution_direction == EXEC_REVERSE || record_core) + +#define RECORD_FILE_MAGIC htonl(0x20090726) /* These are the core struct of record function. @@ -78,9 +86,23 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record with core target. */ +static int record_core = 0; +static gdb_byte *record_core_regbuf; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -103,6 +125,14 @@ static struct target_ops *record_beneath static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, struct target_waitstatus *, int); +static struct target_ops *record_beneath_to_fetch_registers_ops; +static void (*record_beneath_to_fetch_registers) (struct target_ops *, + struct regcache *, + int regno); +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_store_registers_ops; static void (*record_beneath_to_store_registers) (struct target_ops *, struct regcache *, @@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br struct bp_target_info *); static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); +static struct target_ops *record_beneath_to_has_execution_ops; +static int (*record_beneath_to_has_execution) (struct target_ops *ops); +static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache); static void record_list_release (struct record_entry *rec) @@ -169,7 +202,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +419,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + } + + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + break; + } +} + static void record_open (char *name, int from_tty) { @@ -424,8 +542,13 @@ record_open (char *name, int from_tty) if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + if (!strcmp (current_target.to_shortname, "core")) + record_core = 1; + else + record_core = 0; + /* check exec */ - if (!target_has_execution) + if (!target_has_execution && !record_core) error (_("Process record: the program is not being run.")); if (non_stop) error (_("Process record target can't debug inferior in non-stop mode " @@ -454,6 +577,8 @@ record_open (char *name, int from_tty) record_beneath_to_xfer_partial = NULL; record_beneath_to_insert_breakpoint = NULL; record_beneath_to_remove_breakpoint = NULL; + record_beneath_to_has_execution = NULL; + record_beneath_to_prepare_to_store = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) @@ -468,6 +593,11 @@ record_open (char *name, int from_tty) record_beneath_to_wait = t->to_wait; record_beneath_to_wait_ops = t; } + if (!record_beneath_to_fetch_registers) + { + record_beneath_to_fetch_registers = t->to_fetch_registers; + record_beneath_to_fetch_registers_ops = t; + } if (!record_beneath_to_store_registers) { record_beneath_to_store_registers = t->to_store_registers; @@ -482,19 +612,51 @@ record_open (char *name, int from_tty) 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_has_execution) + { + record_beneath_to_has_execution_ops = t; + record_beneath_to_has_execution = t->to_has_execution; + } + if (!record_beneath_to_prepare_to_store) + record_beneath_to_prepare_to_store = t->to_prepare_to_store; } - if (!record_beneath_to_resume) + if (!record_beneath_to_resume && !record_core) error (_("Process record can't get to_resume.")); - if (!record_beneath_to_wait) + if (!record_beneath_to_wait && !record_core) error (_("Process record can't get to_wait.")); - if (!record_beneath_to_store_registers) + if (!record_beneath_to_fetch_registers) + error (_("Process record can't get to_fetch_registers.")); + if (!record_beneath_to_store_registers && !record_core) 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) + if (!record_beneath_to_insert_breakpoint && !record_core) error (_("Process record can't get to_insert_breakpoint.")); - if (!record_beneath_to_remove_breakpoint) + if (!record_beneath_to_remove_breakpoint && !record_core) error (_("Process record can't get to_remove_breakpoint.")); + if (!record_beneath_to_has_execution && !record_core) + error (_("Process record can't get to_has_execution.")); + if (!record_beneath_to_prepare_to_store && !record_core) + error (_("Process record can't get to_prepare_to_store.")); + + if (record_core) + { + /* Get record_core_regbuf. */ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } push_target (&record_ops); @@ -507,10 +669,26 @@ record_open (char *name, int from_tty) static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + xfree (record_core_regbuf); + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -712,76 +890,9 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); - target_kill (); + + if (!record_core) + target_kill (); } /* Record registers change (by user or by GDB) to list as an instruction. */ @@ -945,15 +1058,58 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } static void +record_fetch_registers (struct target_ops *ops, struct regcache *regcache, + int regno) +{ + if (record_core) + { + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + } + else + record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops, + regcache, regno); +} + +static void +record_prepare_to_store (struct regcache *regcache) +{ + if (!record_core) + record_beneath_to_prepare_to_store (regcache); +} + +static void record_store_registers (struct target_ops *ops, struct regcache *regcache, int regno) { + if (record_core) + { + /* Debug with core. */ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); + + return; + } + if (!record_gdb_operation_disable) { if (RECORD_IS_REPLAY) @@ -1000,6 +1156,7 @@ record_store_registers (struct target_op record_registers_change (regcache, regno); } + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, regcache, regno); } @@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops * { if (!record_gdb_operation_disable && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core) { if (RECORD_IS_REPLAY) { @@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } + if (record_core && object == TARGET_OBJECT_MEMORY) + { + /* Debug with core. */ + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return 0; + } + else + error (_("You can't do that without a process to debug.")); + + return 0; + } + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); @@ -1113,6 +1350,15 @@ record_can_execute_reverse (void) return 1; } +int +record_has_execution (struct target_ops *ops) +{ + if (record_core) + return 1; + + return record_beneath_to_has_execution (ops); +} + static void init_record_ops (void) { @@ -1129,11 +1375,14 @@ init_record_ops (void) 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_fetch_registers = record_fetch_registers; + record_ops.to_prepare_to_store = record_prepare_to_store; 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_has_execution = record_has_execution; record_ops.to_stratum = record_stratum; record_ops.to_magic = OPS_MAGIC; } @@ -1154,6 +1403,294 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_fd_cleanups (void *recfdp) +{ + int recfd = *(int *) recfdp; + close (recfd); +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (current_target.to_stratum != record_stratum) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + +/* Load the execution log from a file. */ + +static void +cmd_record_load (char *args, int from_tty) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (current_target.to_stratum != record_stratum) + { + cmd_record_start (NULL, from_tty); + printf_unfiltered (_("Auto start process record.\n")); + } + + if (!args || (args && !*args)) + error (_("Argument for filename required.\n")); + + /* Open the load file. */ + recfd = open (args, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + args, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (args, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), args); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), args); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + /* Get num. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.reg.num = tmpu64; + /* Get val. */ + record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + record_arch_list_add (rec); + break; + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + /* Get addr. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + /* Get len. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + /* Get val. */ + record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len); + record_arch_list_add (rec); + break; + + case record_end: /* end */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), args); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1204,7 +1741,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1244,6 +1781,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1277,6 +1816,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), [-- Attachment #2: prec-dump.txt --] [-- Type: text/plain, Size: 32990 bytes --] --- record.c | 731 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 640 insertions(+), 91 deletions(-) --- a/record.c +++ b/record.c @@ -23,14 +23,22 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) + (record_list->next || execution_direction == EXEC_REVERSE || record_core) + +#define RECORD_FILE_MAGIC htonl(0x20090726) /* These are the core struct of record function. @@ -78,9 +86,23 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record with core target. */ +static int record_core = 0; +static gdb_byte *record_core_regbuf; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -103,6 +125,14 @@ static struct target_ops *record_beneath static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, struct target_waitstatus *, int); +static struct target_ops *record_beneath_to_fetch_registers_ops; +static void (*record_beneath_to_fetch_registers) (struct target_ops *, + struct regcache *, + int regno); +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_store_registers_ops; static void (*record_beneath_to_store_registers) (struct target_ops *, struct regcache *, @@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br struct bp_target_info *); static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); +static struct target_ops *record_beneath_to_has_execution_ops; +static int (*record_beneath_to_has_execution) (struct target_ops *ops); +static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache); static void record_list_release (struct record_entry *rec) @@ -169,7 +202,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +419,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + } + + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + break; + } +} + static void record_open (char *name, int from_tty) { @@ -424,8 +542,13 @@ record_open (char *name, int from_tty) if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + if (!strcmp (current_target.to_shortname, "core")) + record_core = 1; + else + record_core = 0; + /* check exec */ - if (!target_has_execution) + if (!target_has_execution && !record_core) error (_("Process record: the program is not being run.")); if (non_stop) error (_("Process record target can't debug inferior in non-stop mode " @@ -454,6 +577,8 @@ record_open (char *name, int from_tty) record_beneath_to_xfer_partial = NULL; record_beneath_to_insert_breakpoint = NULL; record_beneath_to_remove_breakpoint = NULL; + record_beneath_to_has_execution = NULL; + record_beneath_to_prepare_to_store = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) @@ -468,6 +593,11 @@ record_open (char *name, int from_tty) record_beneath_to_wait = t->to_wait; record_beneath_to_wait_ops = t; } + if (!record_beneath_to_fetch_registers) + { + record_beneath_to_fetch_registers = t->to_fetch_registers; + record_beneath_to_fetch_registers_ops = t; + } if (!record_beneath_to_store_registers) { record_beneath_to_store_registers = t->to_store_registers; @@ -482,19 +612,51 @@ record_open (char *name, int from_tty) 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_has_execution) + { + record_beneath_to_has_execution_ops = t; + record_beneath_to_has_execution = t->to_has_execution; + } + if (!record_beneath_to_prepare_to_store) + record_beneath_to_prepare_to_store = t->to_prepare_to_store; } - if (!record_beneath_to_resume) + if (!record_beneath_to_resume && !record_core) error (_("Process record can't get to_resume.")); - if (!record_beneath_to_wait) + if (!record_beneath_to_wait && !record_core) error (_("Process record can't get to_wait.")); - if (!record_beneath_to_store_registers) + if (!record_beneath_to_fetch_registers) + error (_("Process record can't get to_fetch_registers.")); + if (!record_beneath_to_store_registers && !record_core) 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) + if (!record_beneath_to_insert_breakpoint && !record_core) error (_("Process record can't get to_insert_breakpoint.")); - if (!record_beneath_to_remove_breakpoint) + if (!record_beneath_to_remove_breakpoint && !record_core) error (_("Process record can't get to_remove_breakpoint.")); + if (!record_beneath_to_has_execution && !record_core) + error (_("Process record can't get to_has_execution.")); + if (!record_beneath_to_prepare_to_store && !record_core) + error (_("Process record can't get to_prepare_to_store.")); + + if (record_core) + { + /* Get record_core_regbuf. */ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } push_target (&record_ops); @@ -507,10 +669,26 @@ record_open (char *name, int from_tty) static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + xfree (record_core_regbuf); + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -712,76 +890,9 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); - target_kill (); + + if (!record_core) + target_kill (); } /* Record registers change (by user or by GDB) to list as an instruction. */ @@ -945,15 +1058,58 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } static void +record_fetch_registers (struct target_ops *ops, struct regcache *regcache, + int regno) +{ + if (record_core) + { + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + } + else + record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops, + regcache, regno); +} + +static void +record_prepare_to_store (struct regcache *regcache) +{ + if (!record_core) + record_beneath_to_prepare_to_store (regcache); +} + +static void record_store_registers (struct target_ops *ops, struct regcache *regcache, int regno) { + if (record_core) + { + /* Debug with core. */ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); + + return; + } + if (!record_gdb_operation_disable) { if (RECORD_IS_REPLAY) @@ -1000,6 +1156,7 @@ record_store_registers (struct target_op record_registers_change (regcache, regno); } + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, regcache, regno); } @@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops * { if (!record_gdb_operation_disable && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core) { if (RECORD_IS_REPLAY) { @@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } + if (record_core && object == TARGET_OBJECT_MEMORY) + { + /* Debug with core. */ + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return 0; + } + else + error (_("You can't do that without a process to debug.")); + + return 0; + } + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); @@ -1113,6 +1350,15 @@ record_can_execute_reverse (void) return 1; } +int +record_has_execution (struct target_ops *ops) +{ + if (record_core) + return 1; + + return record_beneath_to_has_execution (ops); +} + static void init_record_ops (void) { @@ -1129,11 +1375,14 @@ init_record_ops (void) 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_fetch_registers = record_fetch_registers; + record_ops.to_prepare_to_store = record_prepare_to_store; 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_has_execution = record_has_execution; record_ops.to_stratum = record_stratum; record_ops.to_magic = OPS_MAGIC; } @@ -1154,6 +1403,294 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_fd_cleanups (void *recfdp) +{ + int recfd = *(int *) recfdp; + close (recfd); +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (current_target.to_stratum != record_stratum) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + +/* Load the execution log from a file. */ + +static void +cmd_record_load (char *args, int from_tty) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (current_target.to_stratum != record_stratum) + { + cmd_record_start (NULL, from_tty); + printf_unfiltered (_("Auto start process record.\n")); + } + + if (!args || (args && !*args)) + error (_("Argument for filename required.\n")); + + /* Open the load file. */ + recfd = open (args, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + args, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (args, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), args); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), args); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + /* Get num. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.reg.num = tmpu64; + /* Get val. */ + record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + record_arch_list_add (rec); + break; + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + /* Get addr. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + /* Get len. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + /* Get val. */ + record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len); + record_arch_list_add (rec); + break; + + case record_end: /* end */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), args); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1204,7 +1741,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1244,6 +1781,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1277,6 +1816,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-05 9:21 ` Hui Zhu @ 2009-08-05 21:23 ` Michael Snyder 2009-08-06 3:14 ` Eli Zaretskii 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-05 21:23 UTC (permalink / raw) To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches Hui Zhu wrote: > +static void > +cmd_record_fd_cleanups (void *recfdp) > +{ > + int recfd = *(int *) recfdp; > + close (recfd); > +} Here's a suggested comment to start documenting the file format: /* Record log save-file format Version 1 Header: 4 bytes: magic number RECORD_FILE_MAGIC. NOTE: be sure to change whenever this file format changes! Records: record_end: 1 byte: record type (record_end) record_reg: 1 byte: record type (record_reg) 8 bytes: register id 16 bytes: register value record_mem: 1 byte: record type (record_mem) 8 bytes: memory address 8 bytes: memory length n bytes: memory value (n == memory length) Version 2 (proposed) [...] */ Below I'll suggest some possible debugging printfs. Feel free to modify them to your own liking. > +static inline void > +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) > +{ > + if (read (fildes, buf, nbyte) != nbyte) > + error (_("Failed to read dump of execution records in '%s'."), > + recfilename); > +} > + > +static inline void > +record_write_dump (char *recfilename, int fildes, const void *buf, > + size_t nbyte) > +{ > + if (write (fildes, buf, nbyte) != nbyte) > + error (_("Failed to write dump of execution records to '%s'."), > + recfilename); > +} > + > +/* Dump the execution log to a file. */ > + > +static void > +cmd_record_dump (char *args, int from_tty) > +{ > + char *recfilename, recfilename_buffer[40]; > + int recfd; > + struct record_entry *cur_record_list; > + uint32_t magic; > + struct regcache *regcache; > + struct gdbarch *gdbarch; > + struct cleanup *old_cleanups; > + struct cleanup *set_cleanups; > + > + if (current_target.to_stratum != record_stratum) > + error (_("Process record is not started.\n")); > + > + if (args && *args) > + recfilename = args; > + else > + { > + /* Default corefile name is "gdb_record.PID". */ > + sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid)); > + recfilename = recfilename_buffer; > + } > + > + /* Open the dump file. */ if (record_debug) fprintf_unfiltered (gdb_stdlog, _("Saving recording to file '%s'\n"), recfilename); > + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, > + S_IRUSR | S_IWUSR); > + if (recfd < 0) > + error (_("Failed to open '%s' for dump execution records: %s"), > + recfilename, strerror (errno)); > + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); > + > + /* Save the current record entry to "cur_record_list". */ > + cur_record_list = record_list; > + > + /* Get the values of regcache and gdbarch. */ > + regcache = get_current_regcache (); > + gdbarch = get_regcache_arch (regcache); > + > + /* Disable the GDB operation record. */ > + set_cleanups = record_gdb_operation_disable_set (); > + > + /* Write the magic code. */ > + magic = RECORD_FILE_MAGIC; if (record_debug) fprintf_unfiltered (gdb_stdlog, _("\ Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), magic); > + record_write_dump (recfilename, recfd, &magic, 4); > + > + /* Reverse execute to the begin of record list. */ > + while (1) > + { > + /* Check for beginning and end of log. */ > + if (record_list == &record_first) > + break; > + > + record_exec_entry (regcache, gdbarch, record_list); > + > + if (record_list->prev) > + record_list = record_list->prev; > + } > + > + /* Dump the entries to recfd and forward execute to the end of > + record list. */ > + while (1) > + { > + /* Dump entry. */ > + if (record_list != &record_first) > + { > + uint8_t tmpu8; > + uint64_t tmpu64; > + > + tmpu8 = record_list->type; > + record_write_dump (recfilename, recfd, &tmpu8, 1); > + > + switch (record_list->type) > + { > + case record_reg: /* reg */ > + tmpu64 = record_list->u.reg.num; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); if (record_debug) fprintf_unfiltered (gdb_stdlog, _("\ Writing register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"), p->u.reg.num, *(ULONGEST *) p->u.reg.val, MAX_REGISTER_SIZE); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + record_write_dump (recfilename, recfd, record_list->u.reg.val, > + MAX_REGISTER_SIZE); > + break; > + case record_mem: /* mem */ > + if (!record_list->u.mem.mem_entry_not_accessible) > + { > + tmpu64 = record_list->u.mem.addr; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); if (record_debug) fprintf_unfiltered (gdb_stdlog, _("\ Writing memory 0x%08x (1 plus 8 plus 8 bytes plus %d bytes)\n"), (unsigned int) p->u.mem.addr, p->u.mem.len); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + tmpu64 = record_list->u.mem.len; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + record_write_dump (recfilename, recfd, > + record_list->u.mem.val, > + record_list->u.mem.len); > + } > + break; case record_end: /* FIXME: record the contents of record_end rec. */ if (record_debug) fprintf_unfiltered (gdb_stdlog, _("\ Writing record_end (1 byte)\n")); break; > + } > + } > + > + /* Execute entry. */ > + record_exec_entry (regcache, gdbarch, record_list); > + > + if (record_list->next) > + record_list = record_list->next; > + else > + break; > + } > + > + /* Reverse execute to cur_record_list. */ > + while (1) > + { > + /* Check for beginning and end of log. */ > + if (record_list == cur_record_list) > + break; > + > + record_exec_entry (regcache, gdbarch, record_list); > + > + if (record_list->prev) > + record_list = record_list->prev; > + } > + > + do_cleanups (set_cleanups); > + do_cleanups (old_cleanups); > + > + /* Succeeded. */ > + fprintf_filtered (gdb_stdout, _("Saved dump of execution " > + "records to `%s'.\n"), > + recfilename); > +} > + > +/* Load the execution log from a file. */ > + > +static void > +cmd_record_load (char *args, int from_tty) > +{ > + int recfd; > + uint32_t magic; > + struct cleanup *old_cleanups; > + struct cleanup *old_cleanups2; > + struct record_entry *rec; > + int insn_number = 0; > + > + if (current_target.to_stratum != record_stratum) > + { > + cmd_record_start (NULL, from_tty); > + printf_unfiltered (_("Auto start process record.\n")); > + } > + > + if (!args || (args && !*args)) > + error (_("Argument for filename required.\n")); > + /* Open the load file. */ if (record_debug) fprintf_unfiltered (gdb_stdlog, _("Restoring recording from file '%s'\n"), args); > + /* Open the load file. */ > + recfd = open (args, O_RDONLY | O_BINARY); > + if (recfd < 0) > + error (_("Failed to open '%s' for loading execution records: %s"), > + args, strerror (errno)); > + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); > + > + /* Check the magic code. */ > + record_read_dump (args, recfd, &magic, 4); > + if (magic != RECORD_FILE_MAGIC) > + error (_("'%s' is not a valid dump of execution records."), args); > + > + /* Load the entries in recfd to the record_arch_list_head and > + record_arch_list_tail. */ > + record_arch_list_head = NULL; > + record_arch_list_tail = NULL; > + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); > + > + while (1) > + { > + int ret; > + uint8_t tmpu8; > + uint64_t tmpu64; > + > + ret = read (recfd, &tmpu8, 1); > + if (ret < 0) > + error (_("Failed to read dump of execution records in '%s'."), args); > + if (ret == 0) > + break; > + > + switch (tmpu8) > + { > + case record_reg: /* reg */ > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_reg; > + /* Get num. */ > + record_read_dump (args, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.reg.num = tmpu64; > + /* Get val. */ > + record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); if (record_debug) fprintf_unfiltered (gdb_stdlog, _("\ Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"), rec->u.reg.num, *(ULONGEST *) rec->u.reg.val, MAX_REGISTER_SIZE); > + record_arch_list_add (rec); > + break; > + case record_mem: /* mem */ > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_mem; > + /* Get addr. */ > + record_read_dump (args, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.mem.addr = tmpu64; > + /* Get len. */ > + record_read_dump (args, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.mem.len = tmpu64; > + rec->u.mem.mem_entry_not_accessible = 0; > + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); > + /* Get val. */ > + record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len); if (record_debug) fprintf_unfiltered (gdb_stdlog, _("\ Reading memory 0x%08x (1 plus 8 plus %d bytes)\n"), (unsigned int) rec->u.mem.addr, rec->u.mem.len); > + record_arch_list_add (rec); > + break; > + > + case record_end: /* end */ > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_end; if (record_debug) fprintf_unfiltered (gdb_stdlog, _("\ Reading record_end (one byte)\n")); > + record_arch_list_add (rec); > + insn_number ++; > + break; > + > + default: > + error (_("Format of '%s' is not right."), args); > + break; > + } > + } > + > + discard_cleanups (old_cleanups2); > + > + /* Add record_arch_list_head to the end of record list. */ > + for (rec = record_list; rec->next; rec = rec->next); > + rec->next = record_arch_list_head; > + record_arch_list_head->prev = rec; > + > + /* Update record_insn_num and record_insn_max_num. */ > + record_insn_num += insn_number; > + if (record_insn_num > record_insn_max_num) > + { > + record_insn_max_num = record_insn_num; > + warning (_("Auto increase record/replay buffer limit to %d."), > + record_insn_max_num); > + } > + > + do_cleanups (old_cleanups); > + > + /* Succeeded. */ > + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args); > +} > + ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-05 21:23 ` Michael Snyder @ 2009-08-06 3:14 ` Eli Zaretskii 2009-08-06 14:16 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Eli Zaretskii @ 2009-08-06 3:14 UTC (permalink / raw) To: Michael Snyder; +Cc: teawater, gdb-patches > Date: Wed, 05 Aug 2009 14:22:10 -0700 > From: Michael Snyder <msnyder@vmware.com> > CC: Eli Zaretskii <eliz@gnu.org>, > "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> > > Here's a suggested comment to start documenting the file format: > > /* Record log save-file format > Version 1 > > Header: > 4 bytes: magic number RECORD_FILE_MAGIC. > NOTE: be sure to change whenever this file format changes! > > Records: > record_end: > 1 byte: record type (record_end) > record_reg: > 1 byte: record type (record_reg) > 8 bytes: register id > 16 bytes: register value > record_mem: > 1 byte: record type (record_mem) > 8 bytes: memory address > 8 bytes: memory length > n bytes: memory value (n == memory length) This doesn't document the byte order. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-06 3:14 ` Eli Zaretskii @ 2009-08-06 14:16 ` Hui Zhu 2009-08-07 3:27 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-06 14:16 UTC (permalink / raw) To: Eli Zaretskii, Michael Snyder; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 39604 bytes --] On Thu, Aug 6, 2009 at 11:14, Eli Zaretskii<eliz@gnu.org> wrote: >> Date: Wed, 05 Aug 2009 14:22:10 -0700 >> From: Michael Snyder <msnyder@vmware.com> >> CC: Eli Zaretskii <eliz@gnu.org>, >> "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> >> >> Here's a suggested comment to start documenting the file format: >> >> /* Record log save-file format >> Version 1 >> >> Header: >> 4 bytes: magic number RECORD_FILE_MAGIC. >> NOTE: be sure to change whenever this file format changes! >> >> Records: >> record_end: >> 1 byte: record type (record_end) >> record_reg: >> 1 byte: record type (record_reg) >> 8 bytes: register id >> 16 bytes: register value >> record_mem: >> 1 byte: record type (record_mem) >> 8 bytes: memory address >> 8 bytes: memory length >> n bytes: memory value (n == memory length) > > This doesn't document the byte order. > I make a new version include byte order. Please help me review it. Thanks, Hui 2009-08-06 Hui Zhu <teawater@gmail.com> Add dump and load command to process record and replay. * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h, byteswap.h, netinet/in.h): Include files. (RECORD_IS_REPLAY): Return true if record_core is true. (RECORD_FILE_MAGIC): New macro. (record_core_buf_entry): New struct. (record_core, record_core_regbuf, record_core_start, record_core_end, record_core_buf_list, record_beneath_to_fetch_registers_ops, record_beneath_to_fetch_registers, record_beneath_to_store_registers_ops, record_beneath_to_store_registers, record_beneath_to_has_execution_ops, record_beneath_to_has_execution, record_beneath_to_prepare_to_store): New variables. (record_list_release_first_insn): Change function record_list_release_first to this name. (record_arch_list_cleanups): New function. (record_message_cleanups): Removed. (record_message): Change to call record_arch_list_cleanups and record_list_release_first_insn. (record_exec_entry): New function. (record_open): Add support for target core. (record_close): Add support for target core. (record_wait): Call function 'record_exec_entry'. (record_kill): Add support for target core. (record_registers_change): Call record_list_release_first_insn. (record_fetch_registers): New function. (record_prepare_to_store): New function. (record_store_registers): Add support for target core. (record_xfer_partial): Add support for target core. (record_has_execution): New function. (init_record_ops): Set record_ops.to_fetch_registers, record_ops.to_prepare_to_store and record_ops.to_has_execution. (cmd_record_fd_cleanups): New function. (cmd_record_dump): New function. (cmd_record_load): New function. (set_record_insn_max_num): Call record_list_release_first_insn. (_initialize_record): Add commands "record dump" and "record load". --- record.c | 806 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 715 insertions(+), 91 deletions(-) --- a/record.c +++ b/record.c @@ -23,14 +23,22 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) + (record_list->next || execution_direction == EXEC_REVERSE || record_core) + +#define RECORD_FILE_MAGIC htonl(0x20090726) /* These are the core struct of record function. @@ -78,9 +86,23 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record with core target. */ +static int record_core = 0; +static gdb_byte *record_core_regbuf; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -103,6 +125,14 @@ static struct target_ops *record_beneath static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, struct target_waitstatus *, int); +static struct target_ops *record_beneath_to_fetch_registers_ops; +static void (*record_beneath_to_fetch_registers) (struct target_ops *, + struct regcache *, + int regno); +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_store_registers_ops; static void (*record_beneath_to_store_registers) (struct target_ops *, struct regcache *, @@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br struct bp_target_info *); static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); +static struct target_ops *record_beneath_to_has_execution_ops; +static int (*record_beneath_to_has_execution) (struct target_ops *ops); +static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache); static void record_list_release (struct record_entry *rec) @@ -169,7 +202,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +419,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + } + + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + break; + } +} + static void record_open (char *name, int from_tty) { @@ -424,8 +542,13 @@ record_open (char *name, int from_tty) if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + if (!strcmp (current_target.to_shortname, "core")) + record_core = 1; + else + record_core = 0; + /* check exec */ - if (!target_has_execution) + if (!target_has_execution && !record_core) error (_("Process record: the program is not being run.")); if (non_stop) error (_("Process record target can't debug inferior in non-stop mode " @@ -454,6 +577,8 @@ record_open (char *name, int from_tty) record_beneath_to_xfer_partial = NULL; record_beneath_to_insert_breakpoint = NULL; record_beneath_to_remove_breakpoint = NULL; + record_beneath_to_has_execution = NULL; + record_beneath_to_prepare_to_store = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) @@ -468,6 +593,11 @@ record_open (char *name, int from_tty) record_beneath_to_wait = t->to_wait; record_beneath_to_wait_ops = t; } + if (!record_beneath_to_fetch_registers) + { + record_beneath_to_fetch_registers = t->to_fetch_registers; + record_beneath_to_fetch_registers_ops = t; + } if (!record_beneath_to_store_registers) { record_beneath_to_store_registers = t->to_store_registers; @@ -482,19 +612,51 @@ record_open (char *name, int from_tty) 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_has_execution) + { + record_beneath_to_has_execution_ops = t; + record_beneath_to_has_execution = t->to_has_execution; + } + if (!record_beneath_to_prepare_to_store) + record_beneath_to_prepare_to_store = t->to_prepare_to_store; } - if (!record_beneath_to_resume) + if (!record_beneath_to_resume && !record_core) error (_("Process record can't get to_resume.")); - if (!record_beneath_to_wait) + if (!record_beneath_to_wait && !record_core) error (_("Process record can't get to_wait.")); - if (!record_beneath_to_store_registers) + if (!record_beneath_to_fetch_registers) + error (_("Process record can't get to_fetch_registers.")); + if (!record_beneath_to_store_registers && !record_core) 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) + if (!record_beneath_to_insert_breakpoint && !record_core) error (_("Process record can't get to_insert_breakpoint.")); - if (!record_beneath_to_remove_breakpoint) + if (!record_beneath_to_remove_breakpoint && !record_core) error (_("Process record can't get to_remove_breakpoint.")); + if (!record_beneath_to_has_execution && !record_core) + error (_("Process record can't get to_has_execution.")); + if (!record_beneath_to_prepare_to_store && !record_core) + error (_("Process record can't get to_prepare_to_store.")); + + if (record_core) + { + /* Get record_core_regbuf. */ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } push_target (&record_ops); @@ -507,10 +669,26 @@ record_open (char *name, int from_tty) static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + xfree (record_core_regbuf); + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -712,76 +890,9 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); - target_kill (); + + if (!record_core) + target_kill (); } /* Record registers change (by user or by GDB) to list as an instruction. */ @@ -945,15 +1058,58 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } static void +record_fetch_registers (struct target_ops *ops, struct regcache *regcache, + int regno) +{ + if (record_core) + { + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + } + else + record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops, + regcache, regno); +} + +static void +record_prepare_to_store (struct regcache *regcache) +{ + if (!record_core) + record_beneath_to_prepare_to_store (regcache); +} + +static void record_store_registers (struct target_ops *ops, struct regcache *regcache, int regno) { + if (record_core) + { + /* Debug with core. */ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); + + return; + } + if (!record_gdb_operation_disable) { if (RECORD_IS_REPLAY) @@ -1000,6 +1156,7 @@ record_store_registers (struct target_op record_registers_change (regcache, regno); } + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, regcache, regno); } @@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops * { if (!record_gdb_operation_disable && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core) { if (RECORD_IS_REPLAY) { @@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } + if (record_core && object == TARGET_OBJECT_MEMORY) + { + /* Debug with core. */ + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return 0; + } + else + error (_("You can't do that without a process to debug.")); + + return 0; + } + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); @@ -1113,6 +1350,15 @@ record_can_execute_reverse (void) return 1; } +int +record_has_execution (struct target_ops *ops) +{ + if (record_core) + return 1; + + return record_beneath_to_has_execution (ops); +} + static void init_record_ops (void) { @@ -1129,11 +1375,14 @@ init_record_ops (void) 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_fetch_registers = record_fetch_registers; + record_ops.to_prepare_to_store = record_prepare_to_store; 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_has_execution = record_has_execution; record_ops.to_stratum = record_stratum; record_ops.to_magic = OPS_MAGIC; } @@ -1154,6 +1403,369 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_fd_cleanups (void *recfdp) +{ + int recfd = *(int *) recfdp; + close (recfd); +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Record log save-file format + Version 1 + + Header: + 4 bytes: magic number htonl(0x20090726). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end). + record_reg: + 1 byte: record type (record_reg). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem). + 8 bytes: memory address (network byte order). + 8 bytes: memory length (network byte order). + n bytes: memory value (n == memory length). +*/ + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (current_target.to_stratum != record_stratum) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Saving recording to file '%s'\n"), + recfilename); + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing register %d (1 plus 8 plus %d bytes)\n"), + record_list->u.reg.num, + MAX_REGISTER_SIZE); + + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (gdbarch, + record_list->u.mem.addr), + record_list->u.mem.len); + + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + + case record_end: + /* FIXME: record the contents of record_end rec. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Writing record_end (1 byte)\n")); + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + +/* Load the execution log from a file. */ + +static void +cmd_record_load (char *args, int from_tty) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (current_target.to_stratum != record_stratum) + { + cmd_record_start (NULL, from_tty); + printf_unfiltered (_("Auto start process record.\n")); + } + + if (!args || (args && !*args)) + error (_("Argument for filename required.\n")); + + /* Open the load file. */ + recfd = open (args, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + args, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (args, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), args); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), args); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + + /* Get num. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.reg.num = tmpu64; + + /* Get val. */ + record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading register %d (1 plus 8 plus %d bytes)\n"), + rec->u.reg.num, + MAX_REGISTER_SIZE); + + record_arch_list_add (rec); + break; + + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + + /* Get addr. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + + /* Get len. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + + /* Get val. */ + record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (get_current_arch (), + rec->u.mem.addr), + rec->u.mem.len); + + record_arch_list_add (rec); + break; + + case record_end: /* end */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Reading record_end (1 byte)\n")); + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), args); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1204,7 +1816,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1244,6 +1856,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1277,6 +1891,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), [-- Attachment #2: prec-dump.txt --] [-- Type: text/plain, Size: 35594 bytes --] --- record.c | 806 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 715 insertions(+), 91 deletions(-) --- a/record.c +++ b/record.c @@ -23,14 +23,22 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ - (record_list->next || execution_direction == EXEC_REVERSE) + (record_list->next || execution_direction == EXEC_REVERSE || record_core) + +#define RECORD_FILE_MAGIC htonl(0x20090726) /* These are the core struct of record function. @@ -78,9 +86,23 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record with core target. */ +static int record_core = 0; +static gdb_byte *record_core_regbuf; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -103,6 +125,14 @@ static struct target_ops *record_beneath static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, struct target_waitstatus *, int); +static struct target_ops *record_beneath_to_fetch_registers_ops; +static void (*record_beneath_to_fetch_registers) (struct target_ops *, + struct regcache *, + int regno); +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_store_registers_ops; static void (*record_beneath_to_store_registers) (struct target_ops *, struct regcache *, @@ -119,6 +149,9 @@ static int (*record_beneath_to_insert_br struct bp_target_info *); static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); +static struct target_ops *record_beneath_to_has_execution_ops; +static int (*record_beneath_to_has_execution) (struct target_ops *ops); +static void (*record_beneath_to_prepare_to_store) (struct regcache *regcache); static void record_list_release (struct record_entry *rec) @@ -169,7 +202,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -340,30 +373,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +419,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,6 +449,91 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !record_core) + || (execution_direction != EXEC_REVERSE && record_core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + } + + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + break; + } +} + static void record_open (char *name, int from_tty) { @@ -424,8 +542,13 @@ record_open (char *name, int from_tty) if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + if (!strcmp (current_target.to_shortname, "core")) + record_core = 1; + else + record_core = 0; + /* check exec */ - if (!target_has_execution) + if (!target_has_execution && !record_core) error (_("Process record: the program is not being run.")); if (non_stop) error (_("Process record target can't debug inferior in non-stop mode " @@ -454,6 +577,8 @@ record_open (char *name, int from_tty) record_beneath_to_xfer_partial = NULL; record_beneath_to_insert_breakpoint = NULL; record_beneath_to_remove_breakpoint = NULL; + record_beneath_to_has_execution = NULL; + record_beneath_to_prepare_to_store = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) @@ -468,6 +593,11 @@ record_open (char *name, int from_tty) record_beneath_to_wait = t->to_wait; record_beneath_to_wait_ops = t; } + if (!record_beneath_to_fetch_registers) + { + record_beneath_to_fetch_registers = t->to_fetch_registers; + record_beneath_to_fetch_registers_ops = t; + } if (!record_beneath_to_store_registers) { record_beneath_to_store_registers = t->to_store_registers; @@ -482,19 +612,51 @@ record_open (char *name, int from_tty) 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_has_execution) + { + record_beneath_to_has_execution_ops = t; + record_beneath_to_has_execution = t->to_has_execution; + } + if (!record_beneath_to_prepare_to_store) + record_beneath_to_prepare_to_store = t->to_prepare_to_store; } - if (!record_beneath_to_resume) + if (!record_beneath_to_resume && !record_core) error (_("Process record can't get to_resume.")); - if (!record_beneath_to_wait) + if (!record_beneath_to_wait && !record_core) error (_("Process record can't get to_wait.")); - if (!record_beneath_to_store_registers) + if (!record_beneath_to_fetch_registers) + error (_("Process record can't get to_fetch_registers.")); + if (!record_beneath_to_store_registers && !record_core) 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) + if (!record_beneath_to_insert_breakpoint && !record_core) error (_("Process record can't get to_insert_breakpoint.")); - if (!record_beneath_to_remove_breakpoint) + if (!record_beneath_to_remove_breakpoint && !record_core) error (_("Process record can't get to_remove_breakpoint.")); + if (!record_beneath_to_has_execution && !record_core) + error (_("Process record can't get to_has_execution.")); + if (!record_beneath_to_prepare_to_store && !record_core) + error (_("Process record can't get to_prepare_to_store.")); + + if (record_core) + { + /* Get record_core_regbuf. */ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } push_target (&record_ops); @@ -507,10 +669,26 @@ record_open (char *name, int from_tty) static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + xfree (record_core_regbuf); + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -712,76 +890,9 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,7 +1012,9 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); - target_kill (); + + if (!record_core) + target_kill (); } /* Record registers change (by user or by GDB) to list as an instruction. */ @@ -945,15 +1058,58 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } static void +record_fetch_registers (struct target_ops *ops, struct regcache *regcache, + int regno) +{ + if (record_core) + { + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + } + else + record_beneath_to_fetch_registers (record_beneath_to_store_registers_ops, + regcache, regno); +} + +static void +record_prepare_to_store (struct regcache *regcache) +{ + if (!record_core) + record_beneath_to_prepare_to_store (regcache); +} + +static void record_store_registers (struct target_ops *ops, struct regcache *regcache, int regno) { + if (record_core) + { + /* Debug with core. */ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); + + return; + } + if (!record_gdb_operation_disable) { if (RECORD_IS_REPLAY) @@ -1000,6 +1156,7 @@ record_store_registers (struct target_op record_registers_change (regcache, regno); } + record_beneath_to_store_registers (record_beneath_to_store_registers_ops, regcache, regno); } @@ -1015,7 +1172,7 @@ record_xfer_partial (struct target_ops * { if (!record_gdb_operation_disable && (object == TARGET_OBJECT_MEMORY - || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) + || object == TARGET_OBJECT_RAW_MEMORY) && writebuf && !record_core) { if (RECORD_IS_REPLAY) { @@ -1059,11 +1216,91 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } + if (record_core && object == TARGET_OBJECT_MEMORY) + { + /* Debug with core. */ + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return 0; + } + else + error (_("You can't do that without a process to debug.")); + + return 0; + } + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); @@ -1113,6 +1350,15 @@ record_can_execute_reverse (void) return 1; } +int +record_has_execution (struct target_ops *ops) +{ + if (record_core) + return 1; + + return record_beneath_to_has_execution (ops); +} + static void init_record_ops (void) { @@ -1129,11 +1375,14 @@ init_record_ops (void) 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_fetch_registers = record_fetch_registers; + record_ops.to_prepare_to_store = record_prepare_to_store; 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_has_execution = record_has_execution; record_ops.to_stratum = record_stratum; record_ops.to_magic = OPS_MAGIC; } @@ -1154,6 +1403,369 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_fd_cleanups (void *recfdp) +{ + int recfd = *(int *) recfdp; + close (recfd); +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Record log save-file format + Version 1 + + Header: + 4 bytes: magic number htonl(0x20090726). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end). + record_reg: + 1 byte: record type (record_reg). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem). + 8 bytes: memory address (network byte order). + 8 bytes: memory length (network byte order). + n bytes: memory value (n == memory length). +*/ + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (current_target.to_stratum != record_stratum) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + sprintf (recfilename_buffer, "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Saving recording to file '%s'\n"), + recfilename); + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing register %d (1 plus 8 plus %d bytes)\n"), + record_list->u.reg.num, + MAX_REGISTER_SIZE); + + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (gdbarch, + record_list->u.mem.addr), + record_list->u.mem.len); + + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + + case record_end: + /* FIXME: record the contents of record_end rec. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Writing record_end (1 byte)\n")); + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + +/* Load the execution log from a file. */ + +static void +cmd_record_load (char *args, int from_tty) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (current_target.to_stratum != record_stratum) + { + cmd_record_start (NULL, from_tty); + printf_unfiltered (_("Auto start process record.\n")); + } + + if (!args || (args && !*args)) + error (_("Argument for filename required.\n")); + + /* Open the load file. */ + recfd = open (args, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + args, strerror (errno)); + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (args, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), args); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), args); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + + /* Get num. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.reg.num = tmpu64; + + /* Get val. */ + record_read_dump (args, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading register %d (1 plus 8 plus %d bytes)\n"), + rec->u.reg.num, + MAX_REGISTER_SIZE); + + record_arch_list_add (rec); + break; + + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + + /* Get addr. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + + /* Get len. */ + record_read_dump (args, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + + /* Get val. */ + record_read_dump (args, recfd, rec->u.mem.val, rec->u.mem.len); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (get_current_arch (), + rec->u.mem.addr), + rec->u.mem.len); + + record_arch_list_add (rec); + break; + + case record_end: /* end */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Reading record_end (1 byte)\n")); + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), args); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", args); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1204,7 +1816,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1244,6 +1856,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1277,6 +1891,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-06 14:16 ` Hui Zhu @ 2009-08-07 3:27 ` Michael Snyder 2009-08-07 3:29 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-07 3:27 UTC (permalink / raw) To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches Hui Zhu wrote: > +/* Load the execution log from a file. */ > + > +static void > +cmd_record_load (char *args, int from_tty) > +{ > + int recfd; > + uint32_t magic; > + struct cleanup *old_cleanups; > + struct cleanup *old_cleanups2; > + struct record_entry *rec; > + int insn_number = 0; > + > + if (current_target.to_stratum != record_stratum) > + { > + cmd_record_start (NULL, from_tty); > + printf_unfiltered (_("Auto start process record.\n")); > + } > + > + if (!args || (args && !*args)) > + error (_("Argument for filename required.\n")); > + > + /* Open the load file. */ > + recfd = open (args, O_RDONLY | O_BINARY); > + if (recfd < 0) > + error (_("Failed to open '%s' for loading execution records: %s"), > + args, strerror (errno)); > + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); > + > + /* Check the magic code. */ > + record_read_dump (args, recfd, &magic, 4); > + if (magic != RECORD_FILE_MAGIC) > + error (_("'%s' is not a valid dump of execution records."), args); > + > + /* Load the entries in recfd to the record_arch_list_head and > + record_arch_list_tail. */ > + record_arch_list_head = NULL; > + record_arch_list_tail = NULL; Hi Hui, It seems to me that you didn't discard the old recording entries before loading the new ones -- you just added the new ones into the existing list. I don't think that is safe. We can't be sure that there is any relationship between any existing recording entries and the new entries from the file. We certainly can't assume that they are consecutive. I think you have to completely clear the record log at this point, before you begin reading new entries from the dump file. What I would suggest is that we add these lines to record_list_release -- and then we can just call that here. @@ -159,6 +159,11 @@ record_list_release (struct record_entry if (rec != &record_first) xfree (rec); + + record_list = &record_first; + record_arch_list_tail = NULL; + record_arch_list_tail = NULL; + record_insn_num = 0; } ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 3:27 ` Michael Snyder @ 2009-08-07 3:29 ` Hui Zhu 2009-08-07 3:34 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-07 3:29 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches About it, I think keep the old one can make the function more flexible. I try it sometime. It can make two or more gdb_record files to one file. What about add a warning to there? When the record list is not empty, output a warning. Thanks, Hui On Fri, Aug 7, 2009 at 11:04, Michael Snyder<msnyder@vmware.com> wrote: > Hui Zhu wrote: > >> +/* Load the execution log from a file. */ >> + >> +static void >> +cmd_record_load (char *args, int from_tty) >> +{ >> + int recfd; >> + uint32_t magic; >> + struct cleanup *old_cleanups; >> + struct cleanup *old_cleanups2; >> + struct record_entry *rec; >> + int insn_number = 0; >> + >> + if (current_target.to_stratum != record_stratum) >> + { >> + cmd_record_start (NULL, from_tty); >> + printf_unfiltered (_("Auto start process record.\n")); >> + } >> + >> + if (!args || (args && !*args)) >> + error (_("Argument for filename required.\n")); >> + >> + /* Open the load file. */ >> + recfd = open (args, O_RDONLY | O_BINARY); >> + if (recfd < 0) >> + error (_("Failed to open '%s' for loading execution records: %s"), >> + args, strerror (errno)); >> + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); >> + >> + /* Check the magic code. */ >> + record_read_dump (args, recfd, &magic, 4); >> + if (magic != RECORD_FILE_MAGIC) >> + error (_("'%s' is not a valid dump of execution records."), args); >> + >> + /* Load the entries in recfd to the record_arch_list_head and >> + record_arch_list_tail. */ >> + record_arch_list_head = NULL; >> + record_arch_list_tail = NULL; > > Hi Hui, > > It seems to me that you didn't discard the old recording entries > before loading the new ones -- you just added the new ones into > the existing list. > > I don't think that is safe. We can't be sure that there is > any relationship between any existing recording entries and > the new entries from the file. We certainly can't assume > that they are consecutive. > > I think you have to completely clear the record log at this point, > before you begin reading new entries from the dump file. > > What I would suggest is that we add these lines to > record_list_release -- and then we can just call that here. > > @@ -159,6 +159,11 @@ record_list_release (struct record_entry > > if (rec != &record_first) > xfree (rec); > + > + record_list = &record_first; > + record_arch_list_tail = NULL; > + record_arch_list_tail = NULL; > + record_insn_num = 0; > } > > > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 3:29 ` Hui Zhu @ 2009-08-07 3:34 ` Michael Snyder 2009-08-07 4:06 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-07 3:34 UTC (permalink / raw) To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches I think it's too easy to get the debugger into an internally inconsistent state. If you don't know *exactly* what you are doing, this is too likely to go wrong. Sometimes we have to choose between flexibility and letting the user shoot himself in the foot. Hui Zhu wrote: > About it, I think keep the old one can make the function more > flexible. I try it sometime. It can make two or more gdb_record files > to one file. > > What about add a warning to there? When the record list is not empty, > output a warning. > > Thanks, > Hui > > On Fri, Aug 7, 2009 at 11:04, Michael Snyder<msnyder@vmware.com> wrote: >> Hui Zhu wrote: >> >>> +/* Load the execution log from a file. */ >>> + >>> +static void >>> +cmd_record_load (char *args, int from_tty) >>> +{ >>> + int recfd; >>> + uint32_t magic; >>> + struct cleanup *old_cleanups; >>> + struct cleanup *old_cleanups2; >>> + struct record_entry *rec; >>> + int insn_number = 0; >>> + >>> + if (current_target.to_stratum != record_stratum) >>> + { >>> + cmd_record_start (NULL, from_tty); >>> + printf_unfiltered (_("Auto start process record.\n")); >>> + } >>> + >>> + if (!args || (args && !*args)) >>> + error (_("Argument for filename required.\n")); >>> + >>> + /* Open the load file. */ >>> + recfd = open (args, O_RDONLY | O_BINARY); >>> + if (recfd < 0) >>> + error (_("Failed to open '%s' for loading execution records: %s"), >>> + args, strerror (errno)); >>> + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); >>> + >>> + /* Check the magic code. */ >>> + record_read_dump (args, recfd, &magic, 4); >>> + if (magic != RECORD_FILE_MAGIC) >>> + error (_("'%s' is not a valid dump of execution records."), args); >>> + >>> + /* Load the entries in recfd to the record_arch_list_head and >>> + record_arch_list_tail. */ >>> + record_arch_list_head = NULL; >>> + record_arch_list_tail = NULL; >> Hi Hui, >> >> It seems to me that you didn't discard the old recording entries >> before loading the new ones -- you just added the new ones into >> the existing list. >> >> I don't think that is safe. We can't be sure that there is >> any relationship between any existing recording entries and >> the new entries from the file. We certainly can't assume >> that they are consecutive. >> >> I think you have to completely clear the record log at this point, >> before you begin reading new entries from the dump file. >> >> What I would suggest is that we add these lines to >> record_list_release -- and then we can just call that here. >> >> @@ -159,6 +159,11 @@ record_list_release (struct record_entry >> >> if (rec != &record_first) >> xfree (rec); >> + >> + record_list = &record_first; >> + record_arch_list_tail = NULL; >> + record_arch_list_tail = NULL; >> + record_insn_num = 0; >> } >> >> >> ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 3:34 ` Michael Snyder @ 2009-08-07 4:06 ` Hui Zhu 2009-08-07 8:41 ` Eli Zaretskii 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-07 4:06 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches I think a warning is clear to most of people. And when he get this warning. He can delete the record list and load again. He will lost nothing. If we delete the old record list, maybe he still need old record. He will lost something. Hui On Fri, Aug 7, 2009 at 11:28, Michael Snyder<msnyder@vmware.com> wrote: > I think it's too easy to get the debugger into an internally > inconsistent state. If you don't know *exactly* what you are > doing, this is too likely to go wrong. > > Sometimes we have to choose between flexibility and letting > the user shoot himself in the foot. > > Hui Zhu wrote: >> >> About it, I think keep the old one can make the function more >> flexible. I try it sometime. It can make two or more gdb_record files >> to one file. >> >> What about add a warning to there? When the record list is not empty, >> output a warning. >> >> Thanks, >> Hui >> >> On Fri, Aug 7, 2009 at 11:04, Michael Snyder<msnyder@vmware.com> wrote: >>> >>> Hui Zhu wrote: >>> >>>> +/* Load the execution log from a file. */ >>>> + >>>> +static void >>>> +cmd_record_load (char *args, int from_tty) >>>> +{ >>>> + int recfd; >>>> + uint32_t magic; >>>> + struct cleanup *old_cleanups; >>>> + struct cleanup *old_cleanups2; >>>> + struct record_entry *rec; >>>> + int insn_number = 0; >>>> + >>>> + if (current_target.to_stratum != record_stratum) >>>> + { >>>> + cmd_record_start (NULL, from_tty); >>>> + printf_unfiltered (_("Auto start process record.\n")); >>>> + } >>>> + >>>> + if (!args || (args && !*args)) >>>> + error (_("Argument for filename required.\n")); >>>> + >>>> + /* Open the load file. */ >>>> + recfd = open (args, O_RDONLY | O_BINARY); >>>> + if (recfd < 0) >>>> + error (_("Failed to open '%s' for loading execution records: %s"), >>>> + args, strerror (errno)); >>>> + old_cleanups = make_cleanup (cmd_record_fd_cleanups, &recfd); >>>> + >>>> + /* Check the magic code. */ >>>> + record_read_dump (args, recfd, &magic, 4); >>>> + if (magic != RECORD_FILE_MAGIC) >>>> + error (_("'%s' is not a valid dump of execution records."), args); >>>> + >>>> + /* Load the entries in recfd to the record_arch_list_head and >>>> + record_arch_list_tail. */ >>>> + record_arch_list_head = NULL; >>>> + record_arch_list_tail = NULL; >>> >>> Hi Hui, >>> >>> It seems to me that you didn't discard the old recording entries >>> before loading the new ones -- you just added the new ones into >>> the existing list. >>> >>> I don't think that is safe. We can't be sure that there is >>> any relationship between any existing recording entries and >>> the new entries from the file. We certainly can't assume >>> that they are consecutive. >>> >>> I think you have to completely clear the record log at this point, >>> before you begin reading new entries from the dump file. >>> >>> What I would suggest is that we add these lines to >>> record_list_release -- and then we can just call that here. >>> >>> @@ -159,6 +159,11 @@ record_list_release (struct record_entry >>> >>> if (rec != &record_first) >>> xfree (rec); >>> + >>> + record_list = &record_first; >>> + record_arch_list_tail = NULL; >>> + record_arch_list_tail = NULL; >>> + record_insn_num = 0; >>> } >>> >>> >>> > > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 4:06 ` Hui Zhu @ 2009-08-07 8:41 ` Eli Zaretskii 2009-08-07 9:53 ` Hui Zhu 2009-08-07 17:42 ` Michael Snyder 0 siblings, 2 replies; 51+ messages in thread From: Eli Zaretskii @ 2009-08-07 8:41 UTC (permalink / raw) To: Hui Zhu; +Cc: msnyder, gdb-patches > From: Hui Zhu <teawater@gmail.com> > Date: Fri, 7 Aug 2009 11:34:20 +0800 > Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> > > I think a warning is clear to most of people. > > And when he get this warning. He can delete the record list and load > again. He will lost nothing. > > If we delete the old record list, maybe he still need old record. He > will lost something. Instead of a warning, how about asking the user whether to discard the old records or keep them? ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 8:41 ` Eli Zaretskii @ 2009-08-07 9:53 ` Hui Zhu 2009-08-07 12:51 ` Eli Zaretskii 2009-08-07 17:42 ` Michael Snyder 1 sibling, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-07 9:53 UTC (permalink / raw) To: Eli Zaretskii; +Cc: msnyder, gdb-patches On Fri, Aug 7, 2009 at 16:39, Eli Zaretskii<eliz@gnu.org> wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Fri, 7 Aug 2009 11:34:20 +0800 >> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> >> >> I think a warning is clear to most of people. >> >> And when he get this warning. He can delete the record list and load >> again. He will lost nothing. >> >> If we delete the old record list, maybe he still need old record. He >> will lost something. > > Instead of a warning, how about asking the user whether to discard the > old records or keep them? > Agree. What about a query? Thanks, Hui ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 9:53 ` Hui Zhu @ 2009-08-07 12:51 ` Eli Zaretskii 2009-08-07 16:22 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Eli Zaretskii @ 2009-08-07 12:51 UTC (permalink / raw) To: Hui Zhu; +Cc: msnyder, gdb-patches > From: Hui Zhu <teawater@gmail.com> > Date: Fri, 7 Aug 2009 17:21:57 +0800 > Cc: msnyder@vmware.com, gdb-patches@sourceware.org > > On Fri, Aug 7, 2009 at 16:39, Eli Zaretskii<eliz@gnu.org> wrote: > >> From: Hui Zhu <teawater@gmail.com> > >> Date: Fri, 7 Aug 2009 11:34:20 +0800 > >> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> > >> > >> I think a warning is clear to most of people. > >> > >> And when he get this warning. Â He can delete the record list and load > >> again. Â He will lost nothing. > >> > >> If we delete the old record list, maybe he still need old record. Â He > >> will lost something. > > > > Instead of a warning, how about asking the user whether to discard the > > old records or keep them? > > > > Agree. What about a query? Sorry, I'm not sure I understand the question. If you are asking how to word the question we ask the user, here's my proposal: Keep the existing execution log? (yes or no) I think the default should be NO, so that this does TRT when invoked non-interactively. If you agree, you need to use `nquery' for this question. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 12:51 ` Eli Zaretskii @ 2009-08-07 16:22 ` Hui Zhu 0 siblings, 0 replies; 51+ messages in thread From: Hui Zhu @ 2009-08-07 16:22 UTC (permalink / raw) To: Eli Zaretskii; +Cc: msnyder, gdb-patches On Fri, Aug 7, 2009 at 17:52, Eli Zaretskii<eliz@gnu.org> wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Fri, 7 Aug 2009 17:21:57 +0800 >> Cc: msnyder@vmware.com, gdb-patches@sourceware.org >> >> On Fri, Aug 7, 2009 at 16:39, Eli Zaretskii<eliz@gnu.org> wrote: >> >> From: Hui Zhu <teawater@gmail.com> >> >> Date: Fri, 7 Aug 2009 11:34:20 +0800 >> >> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> >> >> >> >> I think a warning is clear to most of people. >> >> >> >> And when he get this warning. He can delete the record list and load >> >> again. He will lost nothing. >> >> >> >> If we delete the old record list, maybe he still need old record. He >> >> will lost something. >> > >> > Instead of a warning, how about asking the user whether to discard the >> > old records or keep them? >> > >> >> Agree. What about a query? > > Sorry, I'm not sure I understand the question. If you are asking how > to word the question we ask the user, here's my proposal: > > Keep the existing execution log? (yes or no) > > I think the default should be NO, so that this does TRT when invoked > non-interactively. If you agree, you need to use `nquery' for this > question. > Looks like user interface's people don't like yquery and nquery, maybe we can use query directly. Thanks, Hui ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 8:41 ` Eli Zaretskii 2009-08-07 9:53 ` Hui Zhu @ 2009-08-07 17:42 ` Michael Snyder 2009-08-08 13:28 ` Hui Zhu 1 sibling, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-07 17:42 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Hui Zhu, gdb-patches Eli Zaretskii wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Fri, 7 Aug 2009 11:34:20 +0800 >> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> >> >> I think a warning is clear to most of people. >> >> And when he get this warning. He can delete the record list and load >> again. He will lost nothing. >> >> If we delete the old record list, maybe he still need old record. He >> will lost something. > > Instead of a warning, how about asking the user whether to discard the > old records or keep them? My concern is, in most cases keeping them will be the wrong thing to do. It will be very easy to create an internally inconsistent state, and rather unlikely to create one that is *not* internally inconsistant. Think about it -- we will be concatenating two independent sets of state changes, with no way of knowing that the actual machine state at the end of one is the same as the machine state at the beginning of the other. When these are then replayed, their effect may have little or nothing to do with what the real machine would actually do. To actually get this right, you would have to be *sure* that your target machine is in the exact same state "now" (ie. when you do the load command) as it was at the *beginning* of the previous recording/debugging session. I would rather either make this a separate, "expert mode" command, or better still, leave it for a future patch to extend the basic (and safe) patch that we first accept. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-07 17:42 ` Michael Snyder @ 2009-08-08 13:28 ` Hui Zhu 2009-08-10 3:09 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-08 13:28 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches I think give him a query is very clear. When he load, if there are some record log, it will query to user. He must choice remove the old record log or keep them. He already know what will happen. Thanks, Hui On Sat, Aug 8, 2009 at 01:20, Michael Snyder<msnyder@vmware.com> wrote: > Eli Zaretskii wrote: >>> >>> From: Hui Zhu <teawater@gmail.com> >>> Date: Fri, 7 Aug 2009 11:34:20 +0800 >>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" >>> <gdb-patches@sourceware.org> >>> >>> I think a warning is clear to most of people. >>> >>> And when he get this warning. He can delete the record list and load >>> again. He will lost nothing. >>> >>> If we delete the old record list, maybe he still need old record. He >>> will lost something. >> >> Instead of a warning, how about asking the user whether to discard the >> old records or keep them? > > My concern is, in most cases keeping them will be the wrong thing to do. > It will be very easy to create an internally inconsistent state, and > rather unlikely to create one that is *not* internally inconsistant. > > Think about it -- we will be concatenating two independent sets of > state changes, with no way of knowing that the actual machine state > at the end of one is the same as the machine state at the beginning > of the other. When these are then replayed, their effect may have > little or nothing to do with what the real machine would actually do. > > To actually get this right, you would have to be *sure* that your > target machine is in the exact same state "now" (ie. when you do > the load command) as it was at the *beginning* of the previous > recording/debugging session. > > I would rather either make this a separate, "expert mode" > command, or better still, leave it for a future patch to extend > the basic (and safe) patch that we first accept. > > > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-08 13:28 ` Hui Zhu @ 2009-08-10 3:09 ` Michael Snyder 2009-08-22 17:39 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-10 3:09 UTC (permalink / raw) To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches Hui Zhu wrote: > I think give him a query is very clear. > > When he load, if there are some record log, it will query to user. He > must choice remove the old record log or keep them. He already know > what will happen. This is my opinion. The default should be to remove the old log (not to query). I think this will be both the most common case and the safest. We can maybe add a command option for those who wish not to do that. Anyone else have an opinion? > On Sat, Aug 8, 2009 at 01:20, Michael Snyder<msnyder@vmware.com> wrote: >> Eli Zaretskii wrote: >>>> From: Hui Zhu <teawater@gmail.com> >>>> Date: Fri, 7 Aug 2009 11:34:20 +0800 >>>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" >>>> <gdb-patches@sourceware.org> >>>> >>>> I think a warning is clear to most of people. >>>> >>>> And when he get this warning. He can delete the record list and load >>>> again. He will lost nothing. >>>> >>>> If we delete the old record list, maybe he still need old record. He >>>> will lost something. >>> Instead of a warning, how about asking the user whether to discard the >>> old records or keep them? >> My concern is, in most cases keeping them will be the wrong thing to do. >> It will be very easy to create an internally inconsistent state, and >> rather unlikely to create one that is *not* internally inconsistant. >> >> Think about it -- we will be concatenating two independent sets of >> state changes, with no way of knowing that the actual machine state >> at the end of one is the same as the machine state at the beginning >> of the other. When these are then replayed, their effect may have >> little or nothing to do with what the real machine would actually do. >> >> To actually get this right, you would have to be *sure* that your >> target machine is in the exact same state "now" (ie. when you do >> the load command) as it was at the *beginning* of the previous >> recording/debugging session. >> >> I would rather either make this a separate, "expert mode" >> command, or better still, leave it for a future patch to extend >> the basic (and safe) patch that we first accept. >> >> >> > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-10 3:09 ` Michael Snyder @ 2009-08-22 17:39 ` Hui Zhu 2009-08-23 1:14 ` Hui Zhu 2009-08-23 23:43 ` Michael Snyder 0 siblings, 2 replies; 51+ messages in thread From: Hui Zhu @ 2009-08-22 17:39 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches [-- Attachment #1: Type: text/plain, Size: 45142 bytes --] On Mon, Aug 10, 2009 at 06:56, Michael Snyder<msnyder@vmware.com> wrote: > Hui Zhu wrote: >> >> I think give him a query is very clear. >> >> When he load, if there are some record log, it will query to user. He >> must choice remove the old record log or keep them. He already know >> what will happen. > > This is my opinion. The default should be to remove the old log > (not to query). I think this will be both the most common case > and the safest. We can maybe add a command option for those who > wish not to do that. > > Anyone else have an opinion? > >> On Sat, Aug 8, 2009 at 01:20, Michael Snyder<msnyder@vmware.com> wrote: >>> >>> Eli Zaretskii wrote: >>>>> >>>>> From: Hui Zhu <teawater@gmail.com> >>>>> Date: Fri, 7 Aug 2009 11:34:20 +0800 >>>>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" >>>>> <gdb-patches@sourceware.org> >>>>> >>>>> I think a warning is clear to most of people. >>>>> >>>>> And when he get this warning. He can delete the record list and load >>>>> again. He will lost nothing. >>>>> >>>>> If we delete the old record list, maybe he still need old record. He >>>>> will lost something. >>>> >>>> Instead of a warning, how about asking the user whether to discard the >>>> old records or keep them? >>> >>> My concern is, in most cases keeping them will be the wrong thing to do. >>> It will be very easy to create an internally inconsistent state, and >>> rather unlikely to create one that is *not* internally inconsistant. >>> >>> Think about it -- we will be concatenating two independent sets of >>> state changes, with no way of knowing that the actual machine state >>> at the end of one is the same as the machine state at the beginning >>> of the other. When these are then replayed, their effect may have >>> little or nothing to do with what the real machine would actually do. >>> >>> To actually get this right, you would have to be *sure* that your >>> target machine is in the exact same state "now" (ie. when you do >>> the load command) as it was at the *beginning* of the previous >>> recording/debugging session. >>> >>> I would rather either make this a separate, "expert mode" >>> command, or better still, leave it for a future patch to extend >>> the basic (and safe) patch that we first accept. >>> >>> >>> >> > > Hi Michael, I make a new version patch. It has a lot of changes. Remove record_core and add a new target record_core for core target to make the code more clear. Make the load together with record_open. Please help me review it. Thanks, Hui 2009-08-23 Hui Zhu <teawater@gmail.com> Add dump and load command to process record and replay. * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h, byteswap.h, netinet/in.h): Include files. (RECORD_FILE_MAGIC): New macro. (record_core_buf_entry): New struct. (record_core_ops): New target_ops. (record_list_release_first_insn): Change function record_list_release_first to this name. (record_arch_list_cleanups): New function. (record_message_cleanups): Removed. (record_message): Change to call record_arch_list_cleanups and record_list_release_first_insn. (record_exec_entry, (record_read_dump, record_fd_cleanups, record_load, record_core_open_1, record_open_1): New function. (record_open): Add support for record_core_ops. (record_close): Add support for record_core_ops. (record_wait): Call function 'record_exec_entry' and add support for target core. (record_registers_change): Call record_list_release_first_insn. (record_core_resume, record_core_resume, record_core_kill, record_core_fetch_registers, record_core_prepare_to_store, record_core_store_registers, record_core_xfer_partial, record_core_insert_breakpoint, record_core_remove_breakpoint, record_core_has_execution, init_record_core_ops, cmd_record_load, record_write_dump, cmd_record_dump): New function. (cmd_record_stop): Add support for record_core_ops. (set_record_insn_max_num): Call record_list_release_first_insn. (_initialize_record): Add commands "record dump" and "record load". --- record.c | 970 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 844 insertions(+), 126 deletions(-) --- a/record.c +++ b/record.c @@ -23,15 +23,23 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) +#define RECORD_FILE_MAGIC htonl(0x20090726) + /* These are the core struct of record function. An record_entry is a record of the value change of a register @@ -78,9 +86,22 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record buf with core target. */ +static gdb_byte *record_core_regbuf = NULL; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -94,6 +115,7 @@ static int record_insn_num = 0; /* The target_ops of process record. */ static struct target_ops record_ops; +static struct target_ops record_core_ops; /* The beneath function pointers. */ static struct target_ops *record_beneath_to_resume_ops; @@ -169,7 +191,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +408,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,13 +438,309 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry, int core) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !core) + || (execution_direction != EXEC_REVERSE && core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !core) + || (execution_direction != EXEC_REVERSE && core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + } + + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + break; + } +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + static void -record_open (char *name, int from_tty) +record_fd_cleanups (void *recfdp) { - struct target_ops *t; + int recfd = *(int *) recfdp; + close (recfd); +} - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); +/* Load the execution log from a file. */ + +static void +record_load (char *name) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (!name || (name && !*name)) + return; + + /* Open the load file. */ + recfd = open (name, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + name, strerror (errno)); + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (name, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), name); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), name); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + + /* Get num. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.reg.num = tmpu64; + + /* Get val. */ + record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading register %d (1 plus 8 plus %d bytes)\n"), + rec->u.reg.num, + MAX_REGISTER_SIZE); + + record_arch_list_add (rec); + break; + + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + + /* Get addr. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + + /* Get len. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + + /* Get val. */ + record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (get_current_arch (), + rec->u.mem.addr), + rec->u.mem.len); + + record_arch_list_add (rec); + break; + + case record_end: /* end */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Reading record_end (1 byte)\n")); + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), name); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name); +} + +static struct target_ops *tmp_to_resume_ops; +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal); +static struct target_ops *tmp_to_wait_ops; +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *, + int); +static struct target_ops *tmp_to_store_registers_ops; +static void (*tmp_to_store_registers) (struct target_ops *, + struct regcache *, + int regno); +static struct target_ops *tmp_to_xfer_partial_ops; +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, + enum target_object object, + const char *annex, + gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, + LONGEST len); +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, + struct bp_target_info *); +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, + struct bp_target_info *); + +static void +record_core_open_1 (char *name, int from_tty) +{ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + if (!name || (name && !*name)) + error (_("Argument for gdb record filename required.\n")); + + /* Get record_core_regbuf. */ + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } + + push_target (&record_core_ops); +} + +static void +record_open_1 (char *name, int from_tty) +{ + struct target_ops *t; /* check exec */ if (!target_has_execution) @@ -438,6 +756,28 @@ record_open (char *name, int from_tty) error (_("Process record: the current architecture doesn't support " "record function.")); + if (!tmp_to_resume) + error (_("Process record can't get to_resume.")); + if (!tmp_to_wait) + error (_("Process record can't get to_wait.")); + if (!tmp_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!tmp_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!tmp_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + /* Check if record target is already running. */ if (current_target.to_stratum == record_stratum) { @@ -447,70 +787,102 @@ record_open (char *name, int from_tty) 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; + /* Reset the tmp beneath pointers. */ + tmp_to_resume_ops = NULL; + tmp_to_resume = NULL; + tmp_to_wait_ops = NULL; + tmp_to_wait = NULL; + tmp_to_store_registers_ops = NULL; + tmp_to_store_registers = NULL; + tmp_to_xfer_partial_ops = NULL; + tmp_to_xfer_partial = NULL; + tmp_to_insert_breakpoint = NULL; + tmp_to_remove_breakpoint = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) { - if (!record_beneath_to_resume) + if (!tmp_to_resume) { - record_beneath_to_resume = t->to_resume; - record_beneath_to_resume_ops = t; + tmp_to_resume = t->to_resume; + tmp_to_resume_ops = t; } - if (!record_beneath_to_wait) + if (!tmp_to_wait) { - record_beneath_to_wait = t->to_wait; - record_beneath_to_wait_ops = t; + tmp_to_wait = t->to_wait; + tmp_to_wait_ops = t; } - if (!record_beneath_to_store_registers) + if (!tmp_to_store_registers) { - record_beneath_to_store_registers = t->to_store_registers; - record_beneath_to_store_registers_ops = t; + tmp_to_store_registers = t->to_store_registers; + tmp_to_store_registers_ops = t; } - if (!record_beneath_to_xfer_partial) + if (!tmp_to_xfer_partial) { - record_beneath_to_xfer_partial = t->to_xfer_partial; - record_beneath_to_xfer_partial_ops = t; + tmp_to_xfer_partial = t->to_xfer_partial; + tmp_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 (!tmp_to_insert_breakpoint) + tmp_to_insert_breakpoint = t->to_insert_breakpoint; + if (!tmp_to_remove_breakpoint) + tmp_to_remove_breakpoint = t->to_remove_breakpoint; } - if (!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) + if (!tmp_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); + if (current_target.to_stratum == core_stratum) + record_core_open_1 (name, from_tty); + else + record_open_1 (name, from_tty); /* Reset */ record_insn_num = 0; record_list = &record_first; record_list->next = NULL; + + /* Set the tmp beneath pointers to beneath pointers. */ + record_beneath_to_resume_ops = tmp_to_resume_ops; + record_beneath_to_resume = tmp_to_resume; + record_beneath_to_wait_ops = tmp_to_wait_ops; + record_beneath_to_wait = tmp_to_wait; + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; + record_beneath_to_store_registers = tmp_to_store_registers; + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; + record_beneath_to_xfer_partial = tmp_to_xfer_partial; + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; + + /* Load if there is argument. */ + record_load (name); } static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + if (record_core_regbuf) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + } + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -584,7 +956,7 @@ record_wait (struct target_ops *ops, "record_resume_step = %d\n", record_resume_step); - if (!RECORD_IS_REPLAY) + if (!RECORD_IS_REPLAY && ops != &record_core_ops) { if (record_resume_error) { @@ -712,76 +1084,10 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list, + (ops == &record_core_ops) ? 1 : 0); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,6 +1207,7 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); + target_kill (); } @@ -945,7 +1252,7 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1058,7 +1365,7 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1138,6 +1445,191 @@ init_record_ops (void) } static void +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; +} + +static void +record_core_kill (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); + + unpush_target (&record_core_ops); +} + +static void +record_core_fetch_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); +} + +static void +record_core_prepare_to_store (struct regcache *regcache) +{ +} + +static void +record_core_store_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); +} + +static LONGEST +record_core_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, ULONGEST offset, + LONGEST len) +{ + if (object == TARGET_OBJECT_MEMORY) + { + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return -1; + } + else + error (_("You can't do that without a process to debug.")); + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +static int +record_core_insert_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +static int +record_core_remove_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +int +record_core_has_execution (struct target_ops *ops) +{ + return 1; +} + +static void +init_record_core_ops (void) +{ + record_core_ops.to_shortname = "record_core"; + record_core_ops.to_longname = "Process record and replay target"; + record_core_ops.to_doc = + "Log program while executing and replay execution from log."; + record_core_ops.to_open = record_open; + record_core_ops.to_close = record_close; + record_core_ops.to_resume = record_core_resume; + record_core_ops.to_wait = record_wait; + record_core_ops.to_kill = record_core_kill; + record_core_ops.to_fetch_registers = record_core_fetch_registers; + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; + record_core_ops.to_store_registers = record_core_store_registers; + record_core_ops.to_xfer_partial = record_core_xfer_partial; + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; + record_core_ops.to_has_execution = record_core_has_execution; + record_core_ops.to_stratum = record_stratum; + record_core_ops.to_magic = OPS_MAGIC; +} + +static void show_record_debug (struct ui_file *file, int from_tty, struct cmd_list_element *c, const char *value) { @@ -1153,6 +1645,212 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_load (char *args, int from_tty) +{ + char buf[512]; + + snprintf (buf, 512, "target record %s", args); + execute_command (buf, from_tty); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Record log save-file format + Version 1 + + Header: + 4 bytes: magic number htonl(0x20090726). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end). + record_reg: + 1 byte: record type (record_reg). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem). + 8 bytes: memory address (network byte order). + 8 bytes: memory length (network byte order). + n bytes: memory value (n == memory length). +*/ + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (strcmp (current_target.to_shortname, "record") != 0) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + snprintf (recfilename_buffer, 40, "gdb_record.%d", + PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Saving recording to file '%s'\n"), + recfilename); + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list, 0); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing register %d (1 plus 8 plus %d bytes)\n"), + record_list->u.reg.num, + MAX_REGISTER_SIZE); + + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (gdbarch, + record_list->u.mem.addr), + record_list->u.mem.len); + + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + + case record_end: + /* FIXME: record the contents of record_end rec. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Writing record_end (1 byte)\n")); + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list, 0); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list, 0); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1185,7 +1883,12 @@ cmd_record_stop (char *args, int from_tt { if (!record_list || !from_tty || query (_("Delete recorded log and " "stop recording?"))) - unpush_target (&record_ops); + { + if (strcmp (current_target.to_shortname, "record") == 0) + unpush_target (&record_ops); + else + unpush_target (&record_core_ops); + } } else printf_unfiltered (_("Process record is not started.\n")); @@ -1203,7 +1906,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1243,6 +1946,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1250,6 +1955,8 @@ _initialize_record (void) init_record_ops (); add_target (&record_ops); + init_record_core_ops (); + add_target (&record_core_ops); add_setshow_zinteger_cmd ("record", no_class, &record_debug, _("Set debugging of record/replay feature."), @@ -1259,9 +1966,10 @@ _initialize_record (void) NULL, show_record_debug, &setdebuglist, &showdebuglist); - add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), - &record_cmdlist, "record ", 0, &cmdlist); + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command."), + &record_cmdlist, "record ", 0, &cmdlist); + set_cmd_completer (c, filename_completer); add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, @@ -1276,6 +1984,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), [-- Attachment #2: prec-dump.txt --] [-- Type: text/plain, Size: 39833 bytes --] --- record.c | 970 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 844 insertions(+), 126 deletions(-) --- a/record.c +++ b/record.c @@ -23,15 +23,23 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) +#define RECORD_FILE_MAGIC htonl(0x20090726) + /* These are the core struct of record function. An record_entry is a record of the value change of a register @@ -78,9 +86,22 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record buf with core target. */ +static gdb_byte *record_core_regbuf = NULL; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -94,6 +115,7 @@ static int record_insn_num = 0; /* The target_ops of process record. */ static struct target_ops record_ops; +static struct target_ops record_core_ops; /* The beneath function pointers. */ static struct target_ops *record_beneath_to_resume_ops; @@ -169,7 +191,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +408,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,13 +438,309 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry, int core) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !core) + || (execution_direction != EXEC_REVERSE && core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + if ((execution_direction == EXEC_REVERSE && !core) + || (execution_direction != EXEC_REVERSE && core)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + error (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + } + + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + break; + } +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + static void -record_open (char *name, int from_tty) +record_fd_cleanups (void *recfdp) { - struct target_ops *t; + int recfd = *(int *) recfdp; + close (recfd); +} - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); +/* Load the execution log from a file. */ + +static void +record_load (char *name) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (!name || (name && !*name)) + return; + + /* Open the load file. */ + recfd = open (name, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + name, strerror (errno)); + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (name, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), name); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), name); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + + /* Get num. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.reg.num = tmpu64; + + /* Get val. */ + record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading register %d (1 plus 8 plus %d bytes)\n"), + rec->u.reg.num, + MAX_REGISTER_SIZE); + + record_arch_list_add (rec); + break; + + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + + /* Get addr. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + + /* Get len. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + + /* Get val. */ + record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (get_current_arch (), + rec->u.mem.addr), + rec->u.mem.len); + + record_arch_list_add (rec); + break; + + case record_end: /* end */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Reading record_end (1 byte)\n")); + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), name); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name); +} + +static struct target_ops *tmp_to_resume_ops; +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal); +static struct target_ops *tmp_to_wait_ops; +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *, + int); +static struct target_ops *tmp_to_store_registers_ops; +static void (*tmp_to_store_registers) (struct target_ops *, + struct regcache *, + int regno); +static struct target_ops *tmp_to_xfer_partial_ops; +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, + enum target_object object, + const char *annex, + gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, + LONGEST len); +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, + struct bp_target_info *); +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, + struct bp_target_info *); + +static void +record_core_open_1 (char *name, int from_tty) +{ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + if (!name || (name && !*name)) + error (_("Argument for gdb record filename required.\n")); + + /* Get record_core_regbuf. */ + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } + + push_target (&record_core_ops); +} + +static void +record_open_1 (char *name, int from_tty) +{ + struct target_ops *t; /* check exec */ if (!target_has_execution) @@ -438,6 +756,28 @@ record_open (char *name, int from_tty) error (_("Process record: the current architecture doesn't support " "record function.")); + if (!tmp_to_resume) + error (_("Process record can't get to_resume.")); + if (!tmp_to_wait) + error (_("Process record can't get to_wait.")); + if (!tmp_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!tmp_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!tmp_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + /* Check if record target is already running. */ if (current_target.to_stratum == record_stratum) { @@ -447,70 +787,102 @@ record_open (char *name, int from_tty) 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; + /* Reset the tmp beneath pointers. */ + tmp_to_resume_ops = NULL; + tmp_to_resume = NULL; + tmp_to_wait_ops = NULL; + tmp_to_wait = NULL; + tmp_to_store_registers_ops = NULL; + tmp_to_store_registers = NULL; + tmp_to_xfer_partial_ops = NULL; + tmp_to_xfer_partial = NULL; + tmp_to_insert_breakpoint = NULL; + tmp_to_remove_breakpoint = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) { - if (!record_beneath_to_resume) + if (!tmp_to_resume) { - record_beneath_to_resume = t->to_resume; - record_beneath_to_resume_ops = t; + tmp_to_resume = t->to_resume; + tmp_to_resume_ops = t; } - if (!record_beneath_to_wait) + if (!tmp_to_wait) { - record_beneath_to_wait = t->to_wait; - record_beneath_to_wait_ops = t; + tmp_to_wait = t->to_wait; + tmp_to_wait_ops = t; } - if (!record_beneath_to_store_registers) + if (!tmp_to_store_registers) { - record_beneath_to_store_registers = t->to_store_registers; - record_beneath_to_store_registers_ops = t; + tmp_to_store_registers = t->to_store_registers; + tmp_to_store_registers_ops = t; } - if (!record_beneath_to_xfer_partial) + if (!tmp_to_xfer_partial) { - record_beneath_to_xfer_partial = t->to_xfer_partial; - record_beneath_to_xfer_partial_ops = t; + tmp_to_xfer_partial = t->to_xfer_partial; + tmp_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 (!tmp_to_insert_breakpoint) + tmp_to_insert_breakpoint = t->to_insert_breakpoint; + if (!tmp_to_remove_breakpoint) + tmp_to_remove_breakpoint = t->to_remove_breakpoint; } - if (!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) + if (!tmp_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); + if (current_target.to_stratum == core_stratum) + record_core_open_1 (name, from_tty); + else + record_open_1 (name, from_tty); /* Reset */ record_insn_num = 0; record_list = &record_first; record_list->next = NULL; + + /* Set the tmp beneath pointers to beneath pointers. */ + record_beneath_to_resume_ops = tmp_to_resume_ops; + record_beneath_to_resume = tmp_to_resume; + record_beneath_to_wait_ops = tmp_to_wait_ops; + record_beneath_to_wait = tmp_to_wait; + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; + record_beneath_to_store_registers = tmp_to_store_registers; + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; + record_beneath_to_xfer_partial = tmp_to_xfer_partial; + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; + + /* Load if there is argument. */ + record_load (name); } static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + if (record_core_regbuf) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + } + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -584,7 +956,7 @@ record_wait (struct target_ops *ops, "record_resume_step = %d\n", record_resume_step); - if (!RECORD_IS_REPLAY) + if (!RECORD_IS_REPLAY && ops != &record_core_ops) { if (record_resume_error) { @@ -712,76 +1084,10 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list, + (ops == &record_core_ops) ? 1 : 0); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,6 +1207,7 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); + target_kill (); } @@ -945,7 +1252,7 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1058,7 +1365,7 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1138,6 +1445,191 @@ init_record_ops (void) } static void +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; +} + +static void +record_core_kill (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); + + unpush_target (&record_core_ops); +} + +static void +record_core_fetch_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); +} + +static void +record_core_prepare_to_store (struct regcache *regcache) +{ +} + +static void +record_core_store_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); +} + +static LONGEST +record_core_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, ULONGEST offset, + LONGEST len) +{ + if (object == TARGET_OBJECT_MEMORY) + { + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return -1; + } + else + error (_("You can't do that without a process to debug.")); + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +static int +record_core_insert_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +static int +record_core_remove_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +int +record_core_has_execution (struct target_ops *ops) +{ + return 1; +} + +static void +init_record_core_ops (void) +{ + record_core_ops.to_shortname = "record_core"; + record_core_ops.to_longname = "Process record and replay target"; + record_core_ops.to_doc = + "Log program while executing and replay execution from log."; + record_core_ops.to_open = record_open; + record_core_ops.to_close = record_close; + record_core_ops.to_resume = record_core_resume; + record_core_ops.to_wait = record_wait; + record_core_ops.to_kill = record_core_kill; + record_core_ops.to_fetch_registers = record_core_fetch_registers; + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; + record_core_ops.to_store_registers = record_core_store_registers; + record_core_ops.to_xfer_partial = record_core_xfer_partial; + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; + record_core_ops.to_has_execution = record_core_has_execution; + record_core_ops.to_stratum = record_stratum; + record_core_ops.to_magic = OPS_MAGIC; +} + +static void show_record_debug (struct ui_file *file, int from_tty, struct cmd_list_element *c, const char *value) { @@ -1153,6 +1645,212 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_load (char *args, int from_tty) +{ + char buf[512]; + + snprintf (buf, 512, "target record %s", args); + execute_command (buf, from_tty); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Record log save-file format + Version 1 + + Header: + 4 bytes: magic number htonl(0x20090726). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end). + record_reg: + 1 byte: record type (record_reg). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem). + 8 bytes: memory address (network byte order). + 8 bytes: memory length (network byte order). + n bytes: memory value (n == memory length). +*/ + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (strcmp (current_target.to_shortname, "record") != 0) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + snprintf (recfilename_buffer, 40, "gdb_record.%d", + PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Saving recording to file '%s'\n"), + recfilename); + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list, 0); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing register %d (1 plus 8 plus %d bytes)\n"), + record_list->u.reg.num, + MAX_REGISTER_SIZE); + + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (gdbarch, + record_list->u.mem.addr), + record_list->u.mem.len); + + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + + case record_end: + /* FIXME: record the contents of record_end rec. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Writing record_end (1 byte)\n")); + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list, 0); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list, 0); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1185,7 +1883,12 @@ cmd_record_stop (char *args, int from_tt { if (!record_list || !from_tty || query (_("Delete recorded log and " "stop recording?"))) - unpush_target (&record_ops); + { + if (strcmp (current_target.to_shortname, "record") == 0) + unpush_target (&record_ops); + else + unpush_target (&record_core_ops); + } } else printf_unfiltered (_("Process record is not started.\n")); @@ -1203,7 +1906,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1243,6 +1946,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1250,6 +1955,8 @@ _initialize_record (void) init_record_ops (); add_target (&record_ops); + init_record_core_ops (); + add_target (&record_core_ops); add_setshow_zinteger_cmd ("record", no_class, &record_debug, _("Set debugging of record/replay feature."), @@ -1259,9 +1966,10 @@ _initialize_record (void) NULL, show_record_debug, &setdebuglist, &showdebuglist); - add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), - &record_cmdlist, "record ", 0, &cmdlist); + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command."), + &record_cmdlist, "record ", 0, &cmdlist); + set_cmd_completer (c, filename_completer); add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, @@ -1276,6 +1984,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-22 17:39 ` Hui Zhu @ 2009-08-23 1:14 ` Hui Zhu 2009-08-23 23:43 ` Michael Snyder 1 sibling, 0 replies; 51+ messages in thread From: Hui Zhu @ 2009-08-23 1:14 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches And about record load. I keep it because "record" is a prefix_cmd (If it's not, I can let "record" command load file directly). A file name cannot be the argument of this command. So I keep the command record load. Thanks, Hui On Sun, Aug 23, 2009 at 01:34, Hui Zhu<teawater@gmail.com> wrote: > On Mon, Aug 10, 2009 at 06:56, Michael Snyder<msnyder@vmware.com> wrote: >> Hui Zhu wrote: >>> >>> I think give him a query is very clear. >>> >>> When he load, if there are some record log, it will query to user. He >>> must choice remove the old record log or keep them. He already know >>> what will happen. >> >> This is my opinion. The default should be to remove the old log >> (not to query). I think this will be both the most common case >> and the safest. We can maybe add a command option for those who >> wish not to do that. >> >> Anyone else have an opinion? >> >>> On Sat, Aug 8, 2009 at 01:20, Michael Snyder<msnyder@vmware.com> wrote: >>>> >>>> Eli Zaretskii wrote: >>>>>> >>>>>> From: Hui Zhu <teawater@gmail.com> >>>>>> Date: Fri, 7 Aug 2009 11:34:20 +0800 >>>>>> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" >>>>>> <gdb-patches@sourceware.org> >>>>>> >>>>>> I think a warning is clear to most of people. >>>>>> >>>>>> And when he get this warning. He can delete the record list and load >>>>>> again. He will lost nothing. >>>>>> >>>>>> If we delete the old record list, maybe he still need old record. He >>>>>> will lost something. >>>>> >>>>> Instead of a warning, how about asking the user whether to discard the >>>>> old records or keep them? >>>> >>>> My concern is, in most cases keeping them will be the wrong thing to do. >>>> It will be very easy to create an internally inconsistent state, and >>>> rather unlikely to create one that is *not* internally inconsistant. >>>> >>>> Think about it -- we will be concatenating two independent sets of >>>> state changes, with no way of knowing that the actual machine state >>>> at the end of one is the same as the machine state at the beginning >>>> of the other. When these are then replayed, their effect may have >>>> little or nothing to do with what the real machine would actually do. >>>> >>>> To actually get this right, you would have to be *sure* that your >>>> target machine is in the exact same state "now" (ie. when you do >>>> the load command) as it was at the *beginning* of the previous >>>> recording/debugging session. >>>> >>>> I would rather either make this a separate, "expert mode" >>>> command, or better still, leave it for a future patch to extend >>>> the basic (and safe) patch that we first accept. >>>> >>>> >>>> >>> >> >> > > Hi Michael, > > I make a new version patch. It has a lot of changes. > Remove record_core and add a new target record_core for core target to > make the code more clear. > Make the load together with record_open. > > Please help me review it. > > Thanks, > Hui > > 2009-08-23 Hui Zhu <teawater@gmail.com> > > Add dump and load command to process record and replay. > > * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h, > byteswap.h, netinet/in.h): Include files. > (RECORD_FILE_MAGIC): New macro. > (record_core_buf_entry): New struct. > (record_core_ops): New target_ops. > (record_list_release_first_insn): Change function > record_list_release_first to this name. > (record_arch_list_cleanups): New function. > (record_message_cleanups): Removed. > (record_message): Change to call record_arch_list_cleanups > and record_list_release_first_insn. > (record_exec_entry, (record_read_dump, record_fd_cleanups, > record_load, record_core_open_1, record_open_1): New function. > (record_open): Add support for record_core_ops. > (record_close): Add support for record_core_ops. > (record_wait): Call function 'record_exec_entry' and > add support for target core. > (record_registers_change): Call record_list_release_first_insn. > (record_core_resume, record_core_resume, record_core_kill, > record_core_fetch_registers, record_core_prepare_to_store, > record_core_store_registers, record_core_xfer_partial, > record_core_insert_breakpoint, record_core_remove_breakpoint, > record_core_has_execution, init_record_core_ops, > cmd_record_load, record_write_dump, > cmd_record_dump): New function. > (cmd_record_stop): Add support for record_core_ops. > (set_record_insn_max_num): Call record_list_release_first_insn. > (_initialize_record): Add commands "record dump" > and "record load". > > --- > record.c | 970 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- > 1 file changed, 844 insertions(+), 126 deletions(-) > > --- a/record.c > +++ b/record.c > @@ -23,15 +23,23 @@ > #include "gdbthread.h" > #include "event-top.h" > #include "exceptions.h" > +#include "completer.h" > +#include "arch-utils.h" > +#include "gdbcore.h" > +#include "exec.h" > #include "record.h" > > +#include <byteswap.h> > #include <signal.h> > +#include <netinet/in.h> > > #define DEFAULT_RECORD_INSN_MAX_NUM 200000 > > #define RECORD_IS_REPLAY \ > (record_list->next || execution_direction == EXEC_REVERSE) > > +#define RECORD_FILE_MAGIC htonl(0x20090726) > + > /* These are the core struct of record function. > > An record_entry is a record of the value change of a register > @@ -78,9 +86,22 @@ struct record_entry > } u; > }; > > +struct record_core_buf_entry > +{ > + struct record_core_buf_entry *prev; > + struct target_section *p; > + bfd_byte *buf; > +}; > + > /* This is the debug switch for process record. */ > int record_debug = 0; > > +/* Record buf with core target. */ > +static gdb_byte *record_core_regbuf = NULL; > +static struct target_section *record_core_start; > +static struct target_section *record_core_end; > +static struct record_core_buf_entry *record_core_buf_list = NULL; > + > /* These list is for execution log. */ > static struct record_entry record_first; > static struct record_entry *record_list = &record_first; > @@ -94,6 +115,7 @@ static int record_insn_num = 0; > > /* The target_ops of process record. */ > static struct target_ops record_ops; > +static struct target_ops record_core_ops; > > /* The beneath function pointers. */ > static struct target_ops *record_beneath_to_resume_ops; > @@ -169,7 +191,7 @@ record_list_release_next (void) > } > > static void > -record_list_release_first (void) > +record_list_release_first_insn (void) > { > struct record_entry *tmp = NULL; > enum record_type type; > @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal) > if (q) > record_stop_at_limit = 0; > else > - error (_("Process record: inferior program stopped.")); > + error (_("Process record: stoped by user.")); > } > } > } > } > > +static void > +record_arch_list_cleanups (void *ignore) > +{ > + record_list_release (record_arch_list_tail); > +} > + > /* Before inferior step (when GDB record the running message, inferior > only can step), GDB will call this function to record the values to > record_list. This function will call gdbarch_process_record to > record the running message of inferior and set them to > record_arch_list, and add it to record_list. */ > > -static void > -record_message_cleanups (void *ignore) > -{ > - record_list_release (record_arch_list_tail); > -} > - > static int > record_message (void *args) > { > int ret; > struct regcache *regcache = args; > - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); > + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); > > record_arch_list_head = NULL; > record_arch_list_tail = NULL; > @@ -386,7 +408,7 @@ record_message (void *args) > record_list = record_arch_list_tail; > > if (record_insn_num == record_insn_max_num && record_insn_max_num) > - record_list_release_first (); > + record_list_release_first_insn (); > else > record_insn_num++; > > @@ -416,13 +438,309 @@ record_gdb_operation_disable_set (void) > return old_cleanups; > } > > +static inline void > +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, > + struct record_entry *entry, int core) > +{ > + switch (entry->type) > + { > + case record_reg: /* reg */ > + { > + gdb_byte reg[MAX_REGISTER_SIZE]; > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_reg %s to " > + "inferior num = %d.\n", > + host_address_to_string (entry), > + entry->u.reg.num); > + > + regcache_cooked_read (regcache, entry->u.reg.num, reg); > + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); > + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); > + } > + break; > + > + case record_mem: /* mem */ > + { > + if (!record_list->u.mem.mem_entry_not_accessible) > + { > + gdb_byte *mem = alloca (entry->u.mem.len); > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_mem %s to " > + "inferior addr = %s len = %d.\n", > + host_address_to_string (entry), > + paddress (gdbarch, entry->u.mem.addr), > + record_list->u.mem.len); > + > + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) > + { > + if ((execution_direction == EXEC_REVERSE && !core) > + || (execution_direction != EXEC_REVERSE && core)) > + { > + record_list->u.mem.mem_entry_not_accessible = 1; > + if (record_debug) > + warning (_("Process record: error reading memory at " > + "addr = %s len = %d."), > + paddress (gdbarch, entry->u.mem.addr), > + entry->u.mem.len); > + } > + else > + error (_("Process record: error reading memory at " > + "addr = %s len = %d."), > + paddress (gdbarch, entry->u.mem.addr), > + entry->u.mem.len); > + } > + else > + { > + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, > + entry->u.mem.len)) > + { > + if ((execution_direction == EXEC_REVERSE && !core) > + || (execution_direction != EXEC_REVERSE && core)) > + { > + record_list->u.mem.mem_entry_not_accessible = 1; > + if (record_debug) > + warning (_("Process record: error writing memory at " > + "addr = %s len = %d."), > + paddress (gdbarch, entry->u.mem.addr), > + entry->u.mem.len); > + } > + else > + error (_("Process record: error writing memory at " > + "addr = %s len = %d."), > + paddress (gdbarch, entry->u.mem.addr), > + entry->u.mem.len); > + } > + } > + > + memcpy (entry->u.mem.val, mem, entry->u.mem.len); > + } > + } > + break; > + } > +} > + > +static inline void > +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) > +{ > + if (read (fildes, buf, nbyte) != nbyte) > + error (_("Failed to read dump of execution records in '%s'."), > + recfilename); > +} > + > static void > -record_open (char *name, int from_tty) > +record_fd_cleanups (void *recfdp) > { > - struct target_ops *t; > + int recfd = *(int *) recfdp; > + close (recfd); > +} > > - if (record_debug) > - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); > +/* Load the execution log from a file. */ > + > +static void > +record_load (char *name) > +{ > + int recfd; > + uint32_t magic; > + struct cleanup *old_cleanups; > + struct cleanup *old_cleanups2; > + struct record_entry *rec; > + int insn_number = 0; > + > + if (!name || (name && !*name)) > + return; > + > + /* Open the load file. */ > + recfd = open (name, O_RDONLY | O_BINARY); > + if (recfd < 0) > + error (_("Failed to open '%s' for loading execution records: %s"), > + name, strerror (errno)); > + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); > + > + /* Check the magic code. */ > + record_read_dump (name, recfd, &magic, 4); > + if (magic != RECORD_FILE_MAGIC) > + error (_("'%s' is not a valid dump of execution records."), name); > + > + /* Load the entries in recfd to the record_arch_list_head and > + record_arch_list_tail. */ > + record_arch_list_head = NULL; > + record_arch_list_tail = NULL; > + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); > + > + while (1) > + { > + int ret; > + uint8_t tmpu8; > + uint64_t tmpu64; > + > + ret = read (recfd, &tmpu8, 1); > + if (ret < 0) > + error (_("Failed to read dump of execution records in '%s'."), name); > + if (ret == 0) > + break; > + > + switch (tmpu8) > + { > + case record_reg: /* reg */ > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_reg; > + > + /* Get num. */ > + record_read_dump (name, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.reg.num = tmpu64; > + > + /* Get val. */ > + record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); > + > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Reading register %d (1 plus 8 plus %d bytes)\n"), > + rec->u.reg.num, > + MAX_REGISTER_SIZE); > + > + record_arch_list_add (rec); > + break; > + > + case record_mem: /* mem */ > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_mem; > + > + /* Get addr. */ > + record_read_dump (name, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.mem.addr = tmpu64; > + > + /* Get len. */ > + record_read_dump (name, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.mem.len = tmpu64; > + rec->u.mem.mem_entry_not_accessible = 0; > + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); > + > + /* Get val. */ > + record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len); > + > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), > + paddress (get_current_arch (), > + rec->u.mem.addr), > + rec->u.mem.len); > + > + record_arch_list_add (rec); > + break; > + > + case record_end: /* end */ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + _("Reading record_end (1 byte)\n")); > + > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_end; > + record_arch_list_add (rec); > + insn_number ++; > + break; > + > + default: > + error (_("Format of '%s' is not right."), name); > + break; > + } > + } > + > + discard_cleanups (old_cleanups2); > + > + /* Add record_arch_list_head to the end of record list. */ > + for (rec = record_list; rec->next; rec = rec->next); > + rec->next = record_arch_list_head; > + record_arch_list_head->prev = rec; > + > + /* Update record_insn_num and record_insn_max_num. */ > + record_insn_num += insn_number; > + if (record_insn_num > record_insn_max_num) > + { > + record_insn_max_num = record_insn_num; > + warning (_("Auto increase record/replay buffer limit to %d."), > + record_insn_max_num); > + } > + > + do_cleanups (old_cleanups); > + > + /* Succeeded. */ > + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name); > +} > + > +static struct target_ops *tmp_to_resume_ops; > +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, > + enum target_signal); > +static struct target_ops *tmp_to_wait_ops; > +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, > + struct target_waitstatus *, > + int); > +static struct target_ops *tmp_to_store_registers_ops; > +static void (*tmp_to_store_registers) (struct target_ops *, > + struct regcache *, > + int regno); > +static struct target_ops *tmp_to_xfer_partial_ops; > +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, > + enum target_object object, > + const char *annex, > + gdb_byte *readbuf, > + const gdb_byte *writebuf, > + ULONGEST offset, > + LONGEST len); > +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, > + struct bp_target_info *); > +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, > + struct bp_target_info *); > + > +static void > +record_core_open_1 (char *name, int from_tty) > +{ > + struct regcache *regcache = get_current_regcache (); > + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); > + int i; > + > + if (!name || (name && !*name)) > + error (_("Argument for gdb record filename required.\n")); > + > + /* Get record_core_regbuf. */ > + target_fetch_registers (regcache, -1); > + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); > + for (i = 0; i < regnum; i ++) > + regcache_raw_collect (regcache, i, > + record_core_regbuf + MAX_REGISTER_SIZE * i); > + > + /* Get record_core_start and record_core_end. */ > + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) > + { > + xfree (record_core_regbuf); > + record_core_regbuf = NULL; > + error (_("\"%s\": Can't find sections: %s"), > + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); > + } > + > + push_target (&record_core_ops); > +} > + > +static void > +record_open_1 (char *name, int from_tty) > +{ > + struct target_ops *t; > > /* check exec */ > if (!target_has_execution) > @@ -438,6 +756,28 @@ record_open (char *name, int from_tty) > error (_("Process record: the current architecture doesn't support " > "record function.")); > > + if (!tmp_to_resume) > + error (_("Process record can't get to_resume.")); > + if (!tmp_to_wait) > + error (_("Process record can't get to_wait.")); > + if (!tmp_to_store_registers) > + error (_("Process record can't get to_store_registers.")); > + if (!tmp_to_insert_breakpoint) > + error (_("Process record can't get to_insert_breakpoint.")); > + if (!tmp_to_remove_breakpoint) > + error (_("Process record can't get to_remove_breakpoint.")); > + > + push_target (&record_ops); > +} > + > +static void > +record_open (char *name, int from_tty) > +{ > + struct target_ops *t; > + > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); > + > /* Check if record target is already running. */ > if (current_target.to_stratum == record_stratum) > { > @@ -447,70 +787,102 @@ record_open (char *name, int from_tty) > 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; > + /* Reset the tmp beneath pointers. */ > + tmp_to_resume_ops = NULL; > + tmp_to_resume = NULL; > + tmp_to_wait_ops = NULL; > + tmp_to_wait = NULL; > + tmp_to_store_registers_ops = NULL; > + tmp_to_store_registers = NULL; > + tmp_to_xfer_partial_ops = NULL; > + tmp_to_xfer_partial = NULL; > + tmp_to_insert_breakpoint = NULL; > + tmp_to_remove_breakpoint = NULL; > > /* Set the beneath function pointers. */ > for (t = current_target.beneath; t != NULL; t = t->beneath) > { > - if (!record_beneath_to_resume) > + if (!tmp_to_resume) > { > - record_beneath_to_resume = t->to_resume; > - record_beneath_to_resume_ops = t; > + tmp_to_resume = t->to_resume; > + tmp_to_resume_ops = t; > } > - if (!record_beneath_to_wait) > + if (!tmp_to_wait) > { > - record_beneath_to_wait = t->to_wait; > - record_beneath_to_wait_ops = t; > + tmp_to_wait = t->to_wait; > + tmp_to_wait_ops = t; > } > - if (!record_beneath_to_store_registers) > + if (!tmp_to_store_registers) > { > - record_beneath_to_store_registers = t->to_store_registers; > - record_beneath_to_store_registers_ops = t; > + tmp_to_store_registers = t->to_store_registers; > + tmp_to_store_registers_ops = t; > } > - if (!record_beneath_to_xfer_partial) > + if (!tmp_to_xfer_partial) > { > - record_beneath_to_xfer_partial = t->to_xfer_partial; > - record_beneath_to_xfer_partial_ops = t; > + tmp_to_xfer_partial = t->to_xfer_partial; > + tmp_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 (!tmp_to_insert_breakpoint) > + tmp_to_insert_breakpoint = t->to_insert_breakpoint; > + if (!tmp_to_remove_breakpoint) > + tmp_to_remove_breakpoint = t->to_remove_breakpoint; > } > - if (!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) > + if (!tmp_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); > + if (current_target.to_stratum == core_stratum) > + record_core_open_1 (name, from_tty); > + else > + record_open_1 (name, from_tty); > > /* Reset */ > record_insn_num = 0; > record_list = &record_first; > record_list->next = NULL; > + > + /* Set the tmp beneath pointers to beneath pointers. */ > + record_beneath_to_resume_ops = tmp_to_resume_ops; > + record_beneath_to_resume = tmp_to_resume; > + record_beneath_to_wait_ops = tmp_to_wait_ops; > + record_beneath_to_wait = tmp_to_wait; > + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; > + record_beneath_to_store_registers = tmp_to_store_registers; > + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; > + record_beneath_to_xfer_partial = tmp_to_xfer_partial; > + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; > + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; > + > + /* Load if there is argument. */ > + record_load (name); > } > > static void > record_close (int quitting) > { > + struct record_core_buf_entry *entry; > + > if (record_debug) > fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); > > record_list_release (record_list); > + > + /* Release record_core_regbuf. */ > + if (record_core_regbuf) > + { > + xfree (record_core_regbuf); > + record_core_regbuf = NULL; > + } > + > + /* Release record_core_buf_list. */ > + if (record_core_buf_list) > + { > + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) > + { > + xfree (record_core_buf_list); > + record_core_buf_list = entry; > + } > + record_core_buf_list = NULL; > + } > } > > static int record_resume_step = 0; > @@ -584,7 +956,7 @@ record_wait (struct target_ops *ops, > "record_resume_step = %d\n", > record_resume_step); > > - if (!RECORD_IS_REPLAY) > + if (!RECORD_IS_REPLAY && ops != &record_core_ops) > { > if (record_resume_error) > { > @@ -712,76 +1084,10 @@ record_wait (struct target_ops *ops, > 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 */ > - /* Nothing to do if the entry is flagged not_accessible. */ > - if (!record_list->u.mem.mem_entry_not_accessible) > - { > - 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 = %s len = %d.\n", > - host_address_to_string (record_list), > - paddress (gdbarch, > - record_list->u.mem.addr), > - record_list->u.mem.len); > + record_exec_entry (regcache, gdbarch, record_list, > + (ops == &record_core_ops) ? 1 : 0); > > - if (target_read_memory (record_list->u.mem.addr, mem, > - record_list->u.mem.len)) > - { > - if (execution_direction != EXEC_REVERSE) > - error (_("Process record: error reading memory at " > - "addr = %s len = %d."), > - paddress (gdbarch, record_list->u.mem.addr), > - record_list->u.mem.len); > - else > - /* Read failed -- > - flag entry as not_accessible. */ > - record_list->u.mem.mem_entry_not_accessible = 1; > - } > - else > - { > - if (target_write_memory (record_list->u.mem.addr, > - record_list->u.mem.val, > - record_list->u.mem.len)) > - { > - if (execution_direction != EXEC_REVERSE) > - error (_("Process record: error writing memory at " > - "addr = %s len = %d."), > - paddress (gdbarch, record_list->u.mem.addr), > - record_list->u.mem.len); > - else > - /* Write failed -- > - flag entry as not_accessible. */ > - record_list->u.mem.mem_entry_not_accessible = 1; > - } > - else > - { > - memcpy (record_list->u.mem.val, mem, > - record_list->u.mem.len); > - } > - } > - } > - } > - else > + if (record_list->type == record_end) > { > if (record_debug > 1) > fprintf_unfiltered (gdb_stdlog, > @@ -901,6 +1207,7 @@ record_kill (struct target_ops *ops) > fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); > > unpush_target (&record_ops); > + > target_kill (); > } > > @@ -945,7 +1252,7 @@ record_registers_change (struct regcache > record_list = record_arch_list_tail; > > if (record_insn_num == record_insn_max_num && record_insn_max_num) > - record_list_release_first (); > + record_list_release_first_insn (); > else > record_insn_num++; > } > @@ -1058,7 +1365,7 @@ record_xfer_partial (struct target_ops * > record_list = record_arch_list_tail; > > if (record_insn_num == record_insn_max_num && record_insn_max_num) > - record_list_release_first (); > + record_list_release_first_insn (); > else > record_insn_num++; > } > @@ -1138,6 +1445,191 @@ init_record_ops (void) > } > > static void > +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, > + enum target_signal siggnal) > +{ > + record_resume_step = step; > + record_resume_siggnal = siggnal; > +} > + > +static void > +record_core_kill (struct target_ops *ops) > +{ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); > + > + unpush_target (&record_core_ops); > +} > + > +static void > +record_core_fetch_registers (struct target_ops *ops, > + struct regcache *regcache, > + int regno) > +{ > + if (regno < 0) > + { > + int num = gdbarch_num_regs (get_regcache_arch (regcache)); > + int i; > + > + for (i = 0; i < num; i ++) > + regcache_raw_supply (regcache, i, > + record_core_regbuf + MAX_REGISTER_SIZE * i); > + } > + else > + regcache_raw_supply (regcache, regno, > + record_core_regbuf + MAX_REGISTER_SIZE * regno); > +} > + > +static void > +record_core_prepare_to_store (struct regcache *regcache) > +{ > +} > + > +static void > +record_core_store_registers (struct target_ops *ops, > + struct regcache *regcache, > + int regno) > +{ > + if (record_gdb_operation_disable) > + regcache_raw_collect (regcache, regno, > + record_core_regbuf + MAX_REGISTER_SIZE * regno); > + else > + error (_("You can't do that without a process to debug.")); > +} > + > +static LONGEST > +record_core_xfer_partial (struct target_ops *ops, enum target_object object, > + const char *annex, gdb_byte *readbuf, > + const gdb_byte *writebuf, ULONGEST offset, > + LONGEST len) > +{ > + if (object == TARGET_OBJECT_MEMORY) > + { > + if (record_gdb_operation_disable || !writebuf) > + { > + struct target_section *p; > + for (p = record_core_start; p < record_core_end; p++) > + { > + if (offset >= p->addr) > + { > + struct record_core_buf_entry *entry; > + > + if (offset >= p->endaddr) > + continue; > + > + if (offset + len > p->endaddr) > + len = p->endaddr - offset; > + > + offset -= p->addr; > + > + /* Read readbuf or write writebuf p, offset, len. */ > + /* Check flags. */ > + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR > + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) > + { > + if (readbuf) > + memset (readbuf, 0, len); > + return len; > + } > + /* Get record_core_buf_entry. */ > + for (entry = record_core_buf_list; entry; > + entry = entry->prev) > + if (entry->p == p) > + break; > + if (writebuf) > + { > + if (!entry) > + { > + /* Add a new entry. */ > + entry > + = (struct record_core_buf_entry *) > + xmalloc > + (sizeof (struct record_core_buf_entry)); > + entry->p = p; > + if (!bfd_malloc_and_get_section (p->bfd, > + p->the_bfd_section, > + &entry->buf)) > + { > + xfree (entry); > + return 0; > + } > + entry->prev = record_core_buf_list; > + record_core_buf_list = entry; > + } > + > + memcpy (entry->buf + offset, writebuf, (size_t) len); > + } > + else > + { > + if (!entry) > + return record_beneath_to_xfer_partial > + (record_beneath_to_xfer_partial_ops, > + object, annex, readbuf, writebuf, > + offset, len); > + > + memcpy (readbuf, entry->buf + offset, (size_t) len); > + } > + > + return len; > + } > + } > + > + return -1; > + } > + else > + error (_("You can't do that without a process to debug.")); > + } > + > + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, > + object, annex, readbuf, writebuf, > + offset, len); > +} > + > +static int > +record_core_insert_breakpoint (struct gdbarch *gdbarch, > + struct bp_target_info *bp_tgt) > +{ > + return 0; > +} > + > +static int > +record_core_remove_breakpoint (struct gdbarch *gdbarch, > + struct bp_target_info *bp_tgt) > +{ > + return 0; > +} > + > +int > +record_core_has_execution (struct target_ops *ops) > +{ > + return 1; > +} > + > +static void > +init_record_core_ops (void) > +{ > + record_core_ops.to_shortname = "record_core"; > + record_core_ops.to_longname = "Process record and replay target"; > + record_core_ops.to_doc = > + "Log program while executing and replay execution from log."; > + record_core_ops.to_open = record_open; > + record_core_ops.to_close = record_close; > + record_core_ops.to_resume = record_core_resume; > + record_core_ops.to_wait = record_wait; > + record_core_ops.to_kill = record_core_kill; > + record_core_ops.to_fetch_registers = record_core_fetch_registers; > + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; > + record_core_ops.to_store_registers = record_core_store_registers; > + record_core_ops.to_xfer_partial = record_core_xfer_partial; > + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; > + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; > + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; > + record_core_ops.to_has_execution = record_core_has_execution; > + record_core_ops.to_stratum = record_stratum; > + record_core_ops.to_magic = OPS_MAGIC; > +} > + > +static void > show_record_debug (struct ui_file *file, int from_tty, > struct cmd_list_element *c, const char *value) > { > @@ -1153,6 +1645,212 @@ cmd_record_start (char *args, int from_t > execute_command ("target record", from_tty); > } > > +static void > +cmd_record_load (char *args, int from_tty) > +{ > + char buf[512]; > + > + snprintf (buf, 512, "target record %s", args); > + execute_command (buf, from_tty); > +} > + > +static inline void > +record_write_dump (char *recfilename, int fildes, const void *buf, > + size_t nbyte) > +{ > + if (write (fildes, buf, nbyte) != nbyte) > + error (_("Failed to write dump of execution records to '%s'."), > + recfilename); > +} > + > +/* Record log save-file format > + Version 1 > + > + Header: > + 4 bytes: magic number htonl(0x20090726). > + NOTE: be sure to change whenever this file format changes! > + > + Records: > + record_end: > + 1 byte: record type (record_end). > + record_reg: > + 1 byte: record type (record_reg). > + 8 bytes: register id (network byte order). > + MAX_REGISTER_SIZE bytes: register value. > + record_mem: > + 1 byte: record type (record_mem). > + 8 bytes: memory address (network byte order). > + 8 bytes: memory length (network byte order). > + n bytes: memory value (n == memory length). > +*/ > + > +/* Dump the execution log to a file. */ > + > +static void > +cmd_record_dump (char *args, int from_tty) > +{ > + char *recfilename, recfilename_buffer[40]; > + int recfd; > + struct record_entry *cur_record_list; > + uint32_t magic; > + struct regcache *regcache; > + struct gdbarch *gdbarch; > + struct cleanup *old_cleanups; > + struct cleanup *set_cleanups; > + > + if (strcmp (current_target.to_shortname, "record") != 0) > + error (_("Process record is not started.\n")); > + > + if (args && *args) > + recfilename = args; > + else > + { > + /* Default corefile name is "gdb_record.PID". */ > + snprintf (recfilename_buffer, 40, "gdb_record.%d", > + PIDGET (inferior_ptid)); > + recfilename = recfilename_buffer; > + } > + > + /* Open the dump file. */ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + _("Saving recording to file '%s'\n"), > + recfilename); > + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, > + S_IRUSR | S_IWUSR); > + if (recfd < 0) > + error (_("Failed to open '%s' for dump execution records: %s"), > + recfilename, strerror (errno)); > + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); > + > + /* Save the current record entry to "cur_record_list". */ > + cur_record_list = record_list; > + > + /* Get the values of regcache and gdbarch. */ > + regcache = get_current_regcache (); > + gdbarch = get_regcache_arch (regcache); > + > + /* Disable the GDB operation record. */ > + set_cleanups = record_gdb_operation_disable_set (); > + > + /* Write the magic code. */ > + magic = RECORD_FILE_MAGIC; > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), > + magic); > + record_write_dump (recfilename, recfd, &magic, 4); > + > + /* Reverse execute to the begin of record list. */ > + while (1) > + { > + /* Check for beginning and end of log. */ > + if (record_list == &record_first) > + break; > + > + record_exec_entry (regcache, gdbarch, record_list, 0); > + > + if (record_list->prev) > + record_list = record_list->prev; > + } > + > + /* Dump the entries to recfd and forward execute to the end of > + record list. */ > + while (1) > + { > + /* Dump entry. */ > + if (record_list != &record_first) > + { > + uint8_t tmpu8; > + uint64_t tmpu64; > + > + tmpu8 = record_list->type; > + record_write_dump (recfilename, recfd, &tmpu8, 1); > + > + switch (record_list->type) > + { > + case record_reg: /* reg */ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Writing register %d (1 plus 8 plus %d bytes)\n"), > + record_list->u.reg.num, > + MAX_REGISTER_SIZE); > + > + tmpu64 = record_list->u.reg.num; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + record_write_dump (recfilename, recfd, record_list->u.reg.val, > + MAX_REGISTER_SIZE); > + break; > + > + case record_mem: /* mem */ > + if (!record_list->u.mem.mem_entry_not_accessible) > + { > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), > + paddress (gdbarch, > + record_list->u.mem.addr), > + record_list->u.mem.len); > + > + tmpu64 = record_list->u.mem.addr; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + tmpu64 = record_list->u.mem.len; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + record_write_dump (recfilename, recfd, > + record_list->u.mem.val, > + record_list->u.mem.len); > + } > + break; > + > + case record_end: > + /* FIXME: record the contents of record_end rec. */ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + _("Writing record_end (1 byte)\n")); > + break; > + } > + } > + > + /* Execute entry. */ > + record_exec_entry (regcache, gdbarch, record_list, 0); > + > + if (record_list->next) > + record_list = record_list->next; > + else > + break; > + } > + > + /* Reverse execute to cur_record_list. */ > + while (1) > + { > + /* Check for beginning and end of log. */ > + if (record_list == cur_record_list) > + break; > + > + record_exec_entry (regcache, gdbarch, record_list, 0); > + > + if (record_list->prev) > + record_list = record_list->prev; > + } > + > + do_cleanups (set_cleanups); > + do_cleanups (old_cleanups); > + > + /* Succeeded. */ > + fprintf_filtered (gdb_stdout, _("Saved dump of execution " > + "records to `%s'.\n"), > + recfilename); > +} > + > /* Truncate the record log from the present point > of replay until the end. */ > > @@ -1185,7 +1883,12 @@ cmd_record_stop (char *args, int from_tt > { > if (!record_list || !from_tty || query (_("Delete recorded log and " > "stop recording?"))) > - unpush_target (&record_ops); > + { > + if (strcmp (current_target.to_shortname, "record") == 0) > + unpush_target (&record_ops); > + else > + unpush_target (&record_core_ops); > + } > } > else > printf_unfiltered (_("Process record is not started.\n")); > @@ -1203,7 +1906,7 @@ set_record_insn_max_num (char *args, int > "the first ones?\n")); > > while (record_insn_num > record_insn_max_num) > - record_list_release_first (); > + record_list_release_first_insn (); > } > } > > @@ -1243,6 +1946,8 @@ info_record_command (char *args, int fro > void > _initialize_record (void) > { > + struct cmd_list_element *c; > + > /* Init record_first. */ > record_first.prev = NULL; > record_first.next = NULL; > @@ -1250,6 +1955,8 @@ _initialize_record (void) > > init_record_ops (); > add_target (&record_ops); > + init_record_core_ops (); > + add_target (&record_core_ops); > > add_setshow_zinteger_cmd ("record", no_class, &record_debug, > _("Set debugging of record/replay feature."), > @@ -1259,9 +1966,10 @@ _initialize_record (void) > NULL, show_record_debug, &setdebuglist, > &showdebuglist); > > - add_prefix_cmd ("record", class_obscure, cmd_record_start, > - _("Abbreviated form of \"target record\" command."), > - &record_cmdlist, "record ", 0, &cmdlist); > + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, > + _("Abbreviated form of \"target record\" command."), > + &record_cmdlist, "record ", 0, &cmdlist); > + set_cmd_completer (c, filename_completer); > add_com_alias ("rec", "record", class_obscure, 1); > add_prefix_cmd ("record", class_support, set_record_command, > _("Set record options"), &set_record_cmdlist, > @@ -1276,6 +1984,16 @@ _initialize_record (void) > "info record ", 0, &infolist); > add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); > > + c = add_cmd ("load", class_obscure, cmd_record_load, > + _("Load previously dumped execution records from \ > +a file given as argument."), > + &record_cmdlist); > + set_cmd_completer (c, filename_completer); > + c = add_cmd ("dump", class_obscure, cmd_record_dump, > + _("Dump the execution records to a file.\n\ > +Argument is optional filename. Default filename is > 'gdb_record.<process_id>'."), > + &record_cmdlist); > + set_cmd_completer (c, filename_completer); > > add_cmd ("delete", class_obscure, cmd_record_delete, > _("Delete the rest of execution log and start recording it anew."), > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-22 17:39 ` Hui Zhu 2009-08-23 1:14 ` Hui Zhu @ 2009-08-23 23:43 ` Michael Snyder 2009-08-24 8:20 ` Hui Zhu 1 sibling, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-23 23:43 UTC (permalink / raw) To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches Hui Zhu wrote: > > Hi Michael, > > I make a new version patch. It has a lot of changes. > Remove record_core and add a new target record_core for core target to > make the code more clear. > Make the load together with record_open. > > Please help me review it. Hi Hui, In this review, I'm going to comment only on the parts of the patch that relate to the record_core_ops (ie. pushing the record stratum on top of the core file stratum). Those parts of the patch are much improved. I like this version a lot better. Thanks for reworking it. Here's the one major thing that still worries me: > +static inline void > +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, > + struct record_entry *entry, int core) > +{ > + switch (entry->type) > + { > + case record_reg: /* reg */ > + { > + gdb_byte reg[MAX_REGISTER_SIZE]; > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_reg %s to " > + "inferior num = %d.\n", > + host_address_to_string (entry), > + entry->u.reg.num); > + > + regcache_cooked_read (regcache, entry->u.reg.num, reg); > + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); > + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); > + } > + break; > + > + case record_mem: /* mem */ > + { > + if (!record_list->u.mem.mem_entry_not_accessible) > + { > + gdb_byte *mem = alloca (entry->u.mem.len); > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_mem %s to " > + "inferior addr = %s len = %d.\n", > + host_address_to_string (entry), > + paddress (gdbarch, entry->u.mem.addr), > + record_list->u.mem.len); > + > + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) > + { > + if ((execution_direction == EXEC_REVERSE && !core) > + || (execution_direction != EXEC_REVERSE && core)) > + { It troubles me that we have to do one thing if we're going forward and a different thing if we're going backward, unles it's a core file, and in that case we do the opposite. That's just not elegant. We're really going to do the same thing (swap the contents of target memory with the contents of the record log) regardless of which direction we're going. See below for my suggestion. > + record_list->u.mem.mem_entry_not_accessible = 1; > + if (record_debug) > + warning (_("Process record: error reading memory at " > + "addr = %s len = %d."), > + paddress (gdbarch, entry->u.mem.addr), > + entry->u.mem.len); > + } > + else > + error (_("Process record: error reading memory at " > + "addr = %s len = %d."), > + paddress (gdbarch, entry->u.mem.addr), > + entry->u.mem.len); > + } > + else > + { > + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, > + entry->u.mem.len)) > + { > + if ((execution_direction == EXEC_REVERSE && !core) > + || (execution_direction != EXEC_REVERSE && core)) And the same thing here. What if we just replace the above with this? What do you think? if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) warning (_("Process record: error reading memory at " "addr = %s len = %d."), paddress (gdbarch, entry->u.mem.addr), entry->u.mem.len); } else { if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, entry->u.mem.len)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) warning (_("Process record: error writing memory at " "addr = %s len = %d."), paddress (gdbarch, entry->u.mem.addr), entry->u.mem.len); } } ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-23 23:43 ` Michael Snyder @ 2009-08-24 8:20 ` Hui Zhu 2009-08-24 18:32 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-24 8:20 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches [-- Attachment #1: Type: text/plain, Size: 41907 bytes --] On Mon, Aug 24, 2009 at 07:21, Michael Snyder<msnyder@vmware.com> wrote: > Hui Zhu wrote: > >> >> Hi Michael, >> >> I make a new version patch. It has a lot of changes. >> Remove record_core and add a new target record_core for core target to >> make the code more clear. >> Make the load together with record_open. >> >> Please help me review it. > > Hi Hui, > > In this review, I'm going to comment only on the parts of the > patch that relate to the record_core_ops (ie. pushing the > record stratum on top of the core file stratum). > > Those parts of the patch are much improved. I like this > version a lot better. Thanks for reworking it. > Hi Michael, Thanks for your review. I make a new patch that update the memset code to: if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) warning (_("Process record: error reading memory at " "addr = %s len = %d."), paddress (gdbarch, entry->u.mem.addr), entry->u.mem.len); } else { if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, entry->u.mem.len)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) warning (_("Process record: error writing memory at " "addr = %s len = %d."), paddress (gdbarch, entry->u.mem.addr), entry->u.mem.len); } else memcpy (entry->u.mem.val, mem, entry->u.mem.len); } Please help me review it. Thanks, Hui --- record.c | 951 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 825 insertions(+), 126 deletions(-) --- a/record.c +++ b/record.c @@ -23,15 +23,23 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) +#define RECORD_FILE_MAGIC htonl(0x20090726) + /* These are the core struct of record function. An record_entry is a record of the value change of a register @@ -78,9 +86,22 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record buf with core target. */ +static gdb_byte *record_core_regbuf = NULL; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -94,6 +115,7 @@ static int record_insn_num = 0; /* The target_ops of process record. */ static struct target_ops record_ops; +static struct target_ops record_core_ops; /* The beneath function pointers. */ static struct target_ops *record_beneath_to_resume_ops; @@ -169,7 +191,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +408,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,13 +438,291 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + } + break; + } +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + static void -record_open (char *name, int from_tty) +record_fd_cleanups (void *recfdp) { - struct target_ops *t; + int recfd = *(int *) recfdp; + close (recfd); +} - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); +/* Load the execution log from a file. */ + +static void +record_load (char *name) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (!name || (name && !*name)) + return; + + /* Open the load file. */ + recfd = open (name, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + name, strerror (errno)); + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (name, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), name); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), name); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + + /* Get num. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.reg.num = tmpu64; + + /* Get val. */ + record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading register %d (1 plus 8 plus %d bytes)\n"), + rec->u.reg.num, + MAX_REGISTER_SIZE); + + record_arch_list_add (rec); + break; + + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + + /* Get addr. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + + /* Get len. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + + /* Get val. */ + record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (get_current_arch (), + rec->u.mem.addr), + rec->u.mem.len); + + record_arch_list_add (rec); + break; + + case record_end: /* end */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Reading record_end (1 byte)\n")); + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), name); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name); +} + +static struct target_ops *tmp_to_resume_ops; +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal); +static struct target_ops *tmp_to_wait_ops; +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *, + int); +static struct target_ops *tmp_to_store_registers_ops; +static void (*tmp_to_store_registers) (struct target_ops *, + struct regcache *, + int regno); +static struct target_ops *tmp_to_xfer_partial_ops; +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, + enum target_object object, + const char *annex, + gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, + LONGEST len); +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, + struct bp_target_info *); +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, + struct bp_target_info *); + +static void +record_core_open_1 (char *name, int from_tty) +{ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + if (!name || (name && !*name)) + error (_("Argument for gdb record filename required.\n")); + + /* Get record_core_regbuf. */ + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } + + push_target (&record_core_ops); +} + +static void +record_open_1 (char *name, int from_tty) +{ + struct target_ops *t; /* check exec */ if (!target_has_execution) @@ -438,6 +738,28 @@ record_open (char *name, int from_tty) error (_("Process record: the current architecture doesn't support " "record function.")); + if (!tmp_to_resume) + error (_("Process record can't get to_resume.")); + if (!tmp_to_wait) + error (_("Process record can't get to_wait.")); + if (!tmp_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!tmp_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!tmp_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + /* Check if record target is already running. */ if (current_target.to_stratum == record_stratum) { @@ -447,70 +769,102 @@ record_open (char *name, int from_tty) 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; + /* Reset the tmp beneath pointers. */ + tmp_to_resume_ops = NULL; + tmp_to_resume = NULL; + tmp_to_wait_ops = NULL; + tmp_to_wait = NULL; + tmp_to_store_registers_ops = NULL; + tmp_to_store_registers = NULL; + tmp_to_xfer_partial_ops = NULL; + tmp_to_xfer_partial = NULL; + tmp_to_insert_breakpoint = NULL; + tmp_to_remove_breakpoint = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) { - if (!record_beneath_to_resume) + if (!tmp_to_resume) { - record_beneath_to_resume = t->to_resume; - record_beneath_to_resume_ops = t; + tmp_to_resume = t->to_resume; + tmp_to_resume_ops = t; } - if (!record_beneath_to_wait) + if (!tmp_to_wait) { - record_beneath_to_wait = t->to_wait; - record_beneath_to_wait_ops = t; + tmp_to_wait = t->to_wait; + tmp_to_wait_ops = t; } - if (!record_beneath_to_store_registers) + if (!tmp_to_store_registers) { - record_beneath_to_store_registers = t->to_store_registers; - record_beneath_to_store_registers_ops = t; + tmp_to_store_registers = t->to_store_registers; + tmp_to_store_registers_ops = t; } - if (!record_beneath_to_xfer_partial) + if (!tmp_to_xfer_partial) { - record_beneath_to_xfer_partial = t->to_xfer_partial; - record_beneath_to_xfer_partial_ops = t; + tmp_to_xfer_partial = t->to_xfer_partial; + tmp_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 (!tmp_to_insert_breakpoint) + tmp_to_insert_breakpoint = t->to_insert_breakpoint; + if (!tmp_to_remove_breakpoint) + tmp_to_remove_breakpoint = t->to_remove_breakpoint; } - if (!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) + if (!tmp_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); + if (current_target.to_stratum == core_stratum) + record_core_open_1 (name, from_tty); + else + record_open_1 (name, from_tty); /* Reset */ record_insn_num = 0; record_list = &record_first; record_list->next = NULL; + + /* Set the tmp beneath pointers to beneath pointers. */ + record_beneath_to_resume_ops = tmp_to_resume_ops; + record_beneath_to_resume = tmp_to_resume; + record_beneath_to_wait_ops = tmp_to_wait_ops; + record_beneath_to_wait = tmp_to_wait; + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; + record_beneath_to_store_registers = tmp_to_store_registers; + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; + record_beneath_to_xfer_partial = tmp_to_xfer_partial; + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; + + /* Load if there is argument. */ + record_load (name); } static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + if (record_core_regbuf) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + } + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -584,7 +938,7 @@ record_wait (struct target_ops *ops, "record_resume_step = %d\n", record_resume_step); - if (!RECORD_IS_REPLAY) + if (!RECORD_IS_REPLAY && ops != &record_core_ops) { if (record_resume_error) { @@ -712,76 +1066,9 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,6 +1188,7 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); + target_kill (); } @@ -945,7 +1233,7 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1058,7 +1346,7 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1138,6 +1426,191 @@ init_record_ops (void) } static void +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; +} + +static void +record_core_kill (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); + + unpush_target (&record_core_ops); +} + +static void +record_core_fetch_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); +} + +static void +record_core_prepare_to_store (struct regcache *regcache) +{ +} + +static void +record_core_store_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); +} + +static LONGEST +record_core_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, ULONGEST offset, + LONGEST len) +{ + if (object == TARGET_OBJECT_MEMORY) + { + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return -1; + } + else + error (_("You can't do that without a process to debug.")); + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +static int +record_core_insert_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +static int +record_core_remove_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +int +record_core_has_execution (struct target_ops *ops) +{ + return 1; +} + +static void +init_record_core_ops (void) +{ + record_core_ops.to_shortname = "record_core"; + record_core_ops.to_longname = "Process record and replay target"; + record_core_ops.to_doc = + "Log program while executing and replay execution from log."; + record_core_ops.to_open = record_open; + record_core_ops.to_close = record_close; + record_core_ops.to_resume = record_core_resume; + record_core_ops.to_wait = record_wait; + record_core_ops.to_kill = record_core_kill; + record_core_ops.to_fetch_registers = record_core_fetch_registers; + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; + record_core_ops.to_store_registers = record_core_store_registers; + record_core_ops.to_xfer_partial = record_core_xfer_partial; + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; + record_core_ops.to_has_execution = record_core_has_execution; + record_core_ops.to_stratum = record_stratum; + record_core_ops.to_magic = OPS_MAGIC; +} + +static void show_record_debug (struct ui_file *file, int from_tty, struct cmd_list_element *c, const char *value) { @@ -1153,6 +1626,212 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_load (char *args, int from_tty) +{ + char buf[512]; + + snprintf (buf, 512, "target record %s", args); + execute_command (buf, from_tty); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Record log save-file format + Version 1 + + Header: + 4 bytes: magic number htonl(0x20090726). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end). + record_reg: + 1 byte: record type (record_reg). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem). + 8 bytes: memory address (network byte order). + 8 bytes: memory length (network byte order). + n bytes: memory value (n == memory length). +*/ + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (strcmp (current_target.to_shortname, "record") != 0) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + snprintf (recfilename_buffer, 40, "gdb_record.%d", + PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Saving recording to file '%s'\n"), + recfilename); + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing register %d (1 plus 8 plus %d bytes)\n"), + record_list->u.reg.num, + MAX_REGISTER_SIZE); + + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (gdbarch, + record_list->u.mem.addr), + record_list->u.mem.len); + + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + + case record_end: + /* FIXME: record the contents of record_end rec. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Writing record_end (1 byte)\n")); + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1185,7 +1864,12 @@ cmd_record_stop (char *args, int from_tt { if (!record_list || !from_tty || query (_("Delete recorded log and " "stop recording?"))) - unpush_target (&record_ops); + { + if (strcmp (current_target.to_shortname, "record") == 0) + unpush_target (&record_ops); + else + unpush_target (&record_core_ops); + } } else printf_unfiltered (_("Process record is not started.\n")); @@ -1203,7 +1887,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1243,6 +1927,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1250,6 +1936,8 @@ _initialize_record (void) init_record_ops (); add_target (&record_ops); + init_record_core_ops (); + add_target (&record_core_ops); add_setshow_zinteger_cmd ("record", no_class, &record_debug, _("Set debugging of record/replay feature."), @@ -1259,9 +1947,10 @@ _initialize_record (void) NULL, show_record_debug, &setdebuglist, &showdebuglist); - add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), - &record_cmdlist, "record ", 0, &cmdlist); + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command."), + &record_cmdlist, "record ", 0, &cmdlist); + set_cmd_completer (c, filename_completer); add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, @@ -1276,6 +1965,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), [-- Attachment #2: prec-dump.txt --] [-- Type: text/plain, Size: 38816 bytes --] --- record.c | 951 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 825 insertions(+), 126 deletions(-) --- a/record.c +++ b/record.c @@ -23,15 +23,23 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) +#define RECORD_FILE_MAGIC htonl(0x20090726) + /* These are the core struct of record function. An record_entry is a record of the value change of a register @@ -78,9 +86,22 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record buf with core target. */ +static gdb_byte *record_core_regbuf = NULL; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -94,6 +115,7 @@ static int record_insn_num = 0; /* The target_ops of process record. */ static struct target_ops record_ops; +static struct target_ops record_core_ops; /* The beneath function pointers. */ static struct target_ops *record_beneath_to_resume_ops; @@ -169,7 +191,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +408,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,13 +438,291 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + } + break; + } +} + +static inline void +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) +{ + if (read (fildes, buf, nbyte) != nbyte) + error (_("Failed to read dump of execution records in '%s'."), + recfilename); +} + static void -record_open (char *name, int from_tty) +record_fd_cleanups (void *recfdp) { - struct target_ops *t; + int recfd = *(int *) recfdp; + close (recfd); +} - if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); +/* Load the execution log from a file. */ + +static void +record_load (char *name) +{ + int recfd; + uint32_t magic; + struct cleanup *old_cleanups; + struct cleanup *old_cleanups2; + struct record_entry *rec; + int insn_number = 0; + + if (!name || (name && !*name)) + return; + + /* Open the load file. */ + recfd = open (name, O_RDONLY | O_BINARY); + if (recfd < 0) + error (_("Failed to open '%s' for loading execution records: %s"), + name, strerror (errno)); + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); + + /* Check the magic code. */ + record_read_dump (name, recfd, &magic, 4); + if (magic != RECORD_FILE_MAGIC) + error (_("'%s' is not a valid dump of execution records."), name); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + ret = read (recfd, &tmpu8, 1); + if (ret < 0) + error (_("Failed to read dump of execution records in '%s'."), name); + if (ret == 0) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + + /* Get num. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.reg.num = tmpu64; + + /* Get val. */ + record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading register %d (1 plus 8 plus %d bytes)\n"), + rec->u.reg.num, + MAX_REGISTER_SIZE); + + record_arch_list_add (rec); + break; + + case record_mem: /* mem */ + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + + /* Get addr. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + + /* Get len. */ + record_read_dump (name, recfd, &tmpu64, 8); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.len = tmpu64; + rec->u.mem.mem_entry_not_accessible = 0; + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); + + /* Get val. */ + record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (get_current_arch (), + rec->u.mem.addr), + rec->u.mem.len); + + record_arch_list_add (rec); + break; + + case record_end: /* end */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Reading record_end (1 byte)\n")); + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + record_arch_list_add (rec); + insn_number ++; + break; + + default: + error (_("Format of '%s' is not right."), name); + break; + } + } + + discard_cleanups (old_cleanups2); + + /* Add record_arch_list_head to the end of record list. */ + for (rec = record_list; rec->next; rec = rec->next); + rec->next = record_arch_list_head; + record_arch_list_head->prev = rec; + + /* Update record_insn_num and record_insn_max_num. */ + record_insn_num += insn_number; + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name); +} + +static struct target_ops *tmp_to_resume_ops; +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal); +static struct target_ops *tmp_to_wait_ops; +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *, + int); +static struct target_ops *tmp_to_store_registers_ops; +static void (*tmp_to_store_registers) (struct target_ops *, + struct regcache *, + int regno); +static struct target_ops *tmp_to_xfer_partial_ops; +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, + enum target_object object, + const char *annex, + gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, + LONGEST len); +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, + struct bp_target_info *); +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, + struct bp_target_info *); + +static void +record_core_open_1 (char *name, int from_tty) +{ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + if (!name || (name && !*name)) + error (_("Argument for gdb record filename required.\n")); + + /* Get record_core_regbuf. */ + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } + + push_target (&record_core_ops); +} + +static void +record_open_1 (char *name, int from_tty) +{ + struct target_ops *t; /* check exec */ if (!target_has_execution) @@ -438,6 +738,28 @@ record_open (char *name, int from_tty) error (_("Process record: the current architecture doesn't support " "record function.")); + if (!tmp_to_resume) + error (_("Process record can't get to_resume.")); + if (!tmp_to_wait) + error (_("Process record can't get to_wait.")); + if (!tmp_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!tmp_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!tmp_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + /* Check if record target is already running. */ if (current_target.to_stratum == record_stratum) { @@ -447,70 +769,102 @@ record_open (char *name, int from_tty) 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; + /* Reset the tmp beneath pointers. */ + tmp_to_resume_ops = NULL; + tmp_to_resume = NULL; + tmp_to_wait_ops = NULL; + tmp_to_wait = NULL; + tmp_to_store_registers_ops = NULL; + tmp_to_store_registers = NULL; + tmp_to_xfer_partial_ops = NULL; + tmp_to_xfer_partial = NULL; + tmp_to_insert_breakpoint = NULL; + tmp_to_remove_breakpoint = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) { - if (!record_beneath_to_resume) + if (!tmp_to_resume) { - record_beneath_to_resume = t->to_resume; - record_beneath_to_resume_ops = t; + tmp_to_resume = t->to_resume; + tmp_to_resume_ops = t; } - if (!record_beneath_to_wait) + if (!tmp_to_wait) { - record_beneath_to_wait = t->to_wait; - record_beneath_to_wait_ops = t; + tmp_to_wait = t->to_wait; + tmp_to_wait_ops = t; } - if (!record_beneath_to_store_registers) + if (!tmp_to_store_registers) { - record_beneath_to_store_registers = t->to_store_registers; - record_beneath_to_store_registers_ops = t; + tmp_to_store_registers = t->to_store_registers; + tmp_to_store_registers_ops = t; } - if (!record_beneath_to_xfer_partial) + if (!tmp_to_xfer_partial) { - record_beneath_to_xfer_partial = t->to_xfer_partial; - record_beneath_to_xfer_partial_ops = t; + tmp_to_xfer_partial = t->to_xfer_partial; + tmp_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 (!tmp_to_insert_breakpoint) + tmp_to_insert_breakpoint = t->to_insert_breakpoint; + if (!tmp_to_remove_breakpoint) + tmp_to_remove_breakpoint = t->to_remove_breakpoint; } - if (!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) + if (!tmp_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); + if (current_target.to_stratum == core_stratum) + record_core_open_1 (name, from_tty); + else + record_open_1 (name, from_tty); /* Reset */ record_insn_num = 0; record_list = &record_first; record_list->next = NULL; + + /* Set the tmp beneath pointers to beneath pointers. */ + record_beneath_to_resume_ops = tmp_to_resume_ops; + record_beneath_to_resume = tmp_to_resume; + record_beneath_to_wait_ops = tmp_to_wait_ops; + record_beneath_to_wait = tmp_to_wait; + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; + record_beneath_to_store_registers = tmp_to_store_registers; + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; + record_beneath_to_xfer_partial = tmp_to_xfer_partial; + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; + + /* Load if there is argument. */ + record_load (name); } static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + if (record_core_regbuf) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + } + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -584,7 +938,7 @@ record_wait (struct target_ops *ops, "record_resume_step = %d\n", record_resume_step); - if (!RECORD_IS_REPLAY) + if (!RECORD_IS_REPLAY && ops != &record_core_ops) { if (record_resume_error) { @@ -712,76 +1066,9 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,6 +1188,7 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); + target_kill (); } @@ -945,7 +1233,7 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1058,7 +1346,7 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1138,6 +1426,191 @@ init_record_ops (void) } static void +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; +} + +static void +record_core_kill (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); + + unpush_target (&record_core_ops); +} + +static void +record_core_fetch_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); +} + +static void +record_core_prepare_to_store (struct regcache *regcache) +{ +} + +static void +record_core_store_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); +} + +static LONGEST +record_core_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, ULONGEST offset, + LONGEST len) +{ + if (object == TARGET_OBJECT_MEMORY) + { + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return -1; + } + else + error (_("You can't do that without a process to debug.")); + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +static int +record_core_insert_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +static int +record_core_remove_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +int +record_core_has_execution (struct target_ops *ops) +{ + return 1; +} + +static void +init_record_core_ops (void) +{ + record_core_ops.to_shortname = "record_core"; + record_core_ops.to_longname = "Process record and replay target"; + record_core_ops.to_doc = + "Log program while executing and replay execution from log."; + record_core_ops.to_open = record_open; + record_core_ops.to_close = record_close; + record_core_ops.to_resume = record_core_resume; + record_core_ops.to_wait = record_wait; + record_core_ops.to_kill = record_core_kill; + record_core_ops.to_fetch_registers = record_core_fetch_registers; + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; + record_core_ops.to_store_registers = record_core_store_registers; + record_core_ops.to_xfer_partial = record_core_xfer_partial; + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; + record_core_ops.to_has_execution = record_core_has_execution; + record_core_ops.to_stratum = record_stratum; + record_core_ops.to_magic = OPS_MAGIC; +} + +static void show_record_debug (struct ui_file *file, int from_tty, struct cmd_list_element *c, const char *value) { @@ -1153,6 +1626,212 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +static void +cmd_record_load (char *args, int from_tty) +{ + char buf[512]; + + snprintf (buf, 512, "target record %s", args); + execute_command (buf, from_tty); +} + +static inline void +record_write_dump (char *recfilename, int fildes, const void *buf, + size_t nbyte) +{ + if (write (fildes, buf, nbyte) != nbyte) + error (_("Failed to write dump of execution records to '%s'."), + recfilename); +} + +/* Record log save-file format + Version 1 + + Header: + 4 bytes: magic number htonl(0x20090726). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end). + record_reg: + 1 byte: record type (record_reg). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem). + 8 bytes: memory address (network byte order). + 8 bytes: memory length (network byte order). + n bytes: memory value (n == memory length). +*/ + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + + if (strcmp (current_target.to_shortname, "record") != 0) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default corefile name is "gdb_record.PID". */ + snprintf (recfilename_buffer, 40, "gdb_record.%d", + PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Saving recording to file '%s'\n"), + recfilename); + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + S_IRUSR | S_IWUSR); + if (recfd < 0) + error (_("Failed to open '%s' for dump execution records: %s"), + recfilename, strerror (errno)); + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + record_write_dump (recfilename, recfd, &magic, 4); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + record_write_dump (recfilename, recfd, &tmpu8, 1); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing register %d (1 plus 8 plus %d bytes)\n"), + record_list->u.reg.num, + MAX_REGISTER_SIZE); + + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, record_list->u.reg.val, + MAX_REGISTER_SIZE); + break; + + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (gdbarch, + record_list->u.mem.addr), + record_list->u.mem.len); + + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + record_write_dump (recfilename, recfd, &tmpu64, 8); + + record_write_dump (recfilename, recfd, + record_list->u.mem.val, + record_list->u.mem.len); + } + break; + + case record_end: + /* FIXME: record the contents of record_end rec. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Writing record_end (1 byte)\n")); + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved dump of execution " + "records to `%s'.\n"), + recfilename); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1185,7 +1864,12 @@ cmd_record_stop (char *args, int from_tt { if (!record_list || !from_tty || query (_("Delete recorded log and " "stop recording?"))) - unpush_target (&record_ops); + { + if (strcmp (current_target.to_shortname, "record") == 0) + unpush_target (&record_ops); + else + unpush_target (&record_core_ops); + } } else printf_unfiltered (_("Process record is not started.\n")); @@ -1203,7 +1887,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1243,6 +1927,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1250,6 +1936,8 @@ _initialize_record (void) init_record_ops (); add_target (&record_ops); + init_record_core_ops (); + add_target (&record_core_ops); add_setshow_zinteger_cmd ("record", no_class, &record_debug, _("Set debugging of record/replay feature."), @@ -1259,9 +1947,10 @@ _initialize_record (void) NULL, show_record_debug, &setdebuglist, &showdebuglist); - add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), - &record_cmdlist, "record ", 0, &cmdlist); + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command."), + &record_cmdlist, "record ", 0, &cmdlist); + set_cmd_completer (c, filename_completer); add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, @@ -1276,6 +1965,16 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("load", class_obscure, cmd_record_load, + _("Load previously dumped execution records from \ +a file given as argument."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-24 8:20 ` Hui Zhu @ 2009-08-24 18:32 ` Michael Snyder 2009-08-25 8:47 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-24 18:32 UTC (permalink / raw) To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches Hui Zhu wrote: > On Mon, Aug 24, 2009 at 07:21, Michael Snyder<msnyder@vmware.com> wrote: >> Hui Zhu wrote: >> >>> Hi Michael, >>> >>> I make a new version patch. It has a lot of changes. >>> Remove record_core and add a new target record_core for core target to >>> make the code more clear. >>> Make the load together with record_open. >>> >>> Please help me review it. >> Hi Hui, >> >> In this review, I'm going to comment only on the parts of the >> patch that relate to the record_core_ops (ie. pushing the >> record stratum on top of the core file stratum). >> >> Those parts of the patch are much improved. I like this >> version a lot better. Thanks for reworking it. >> > > > Hi Michael, > > Thanks for your review. I make a new patch that update the memset code to: Way cool, thanks! Now I can comment on the dump/load part of the patch. It does not seem as if previous issues have been addressed: 1) Core file and record file are separate, no way to verify that they correspond to each other. 2) Previous log not cleared when loading a new log. Have you considered combining this patch with the idea that I proposed -- storing the record log in the core file? Seems like that would be very easy now -- gdb already has the core file open, and there is a global pointer to the BFD. See gdbcore.h:extern bfd* core_bfd; In fact, just for fun, I've merged the two patches in my local tree. It seems to work quite well. I can just post it here if you like, or here's a hint to show you how it goes: static void record_load (void) { int recfd; uint32_t magic; struct cleanup *old_cleanups2; struct record_entry *rec; int insn_number = 0; asection *osec; void nullify_last_target_wait_ptid (void); /* We load the execution log from the open core bfd, if there is one. */ if (core_bfd == NULL) return; if (record_debug) fprintf_filtered (gdb_stdlog, _("Restoring recording from core file.\n")); /* Now need to find our special note section. */ osec = bfd_get_section_by_name (core_bfd, "null0"); > if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) > { > record_list->u.mem.mem_entry_not_accessible = 1; > if (record_debug) > warning (_("Process record: error reading memory at " > "addr = %s len = %d."), > paddress (gdbarch, entry->u.mem.addr), > entry->u.mem.len); > } > else > { > if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, > entry->u.mem.len)) > { > record_list->u.mem.mem_entry_not_accessible = 1; > if (record_debug) > warning (_("Process record: error writing memory at " > "addr = %s len = %d."), > paddress (gdbarch, entry->u.mem.addr), > entry->u.mem.len); > } > else > memcpy (entry->u.mem.val, mem, entry->u.mem.len); > } > > Please help me review it. > > Thanks, > Hui > > --- > record.c | 951 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- > 1 file changed, 825 insertions(+), 126 deletions(-) > > --- a/record.c > +++ b/record.c > @@ -23,15 +23,23 @@ > #include "gdbthread.h" > #include "event-top.h" > #include "exceptions.h" > +#include "completer.h" > +#include "arch-utils.h" > +#include "gdbcore.h" > +#include "exec.h" > #include "record.h" > > +#include <byteswap.h> > #include <signal.h> > +#include <netinet/in.h> > > #define DEFAULT_RECORD_INSN_MAX_NUM 200000 > > #define RECORD_IS_REPLAY \ > (record_list->next || execution_direction == EXEC_REVERSE) > > +#define RECORD_FILE_MAGIC htonl(0x20090726) > + > /* These are the core struct of record function. > > An record_entry is a record of the value change of a register > @@ -78,9 +86,22 @@ struct record_entry > } u; > }; > > +struct record_core_buf_entry > +{ > + struct record_core_buf_entry *prev; > + struct target_section *p; > + bfd_byte *buf; > +}; > + > /* This is the debug switch for process record. */ > int record_debug = 0; > > +/* Record buf with core target. */ > +static gdb_byte *record_core_regbuf = NULL; > +static struct target_section *record_core_start; > +static struct target_section *record_core_end; > +static struct record_core_buf_entry *record_core_buf_list = NULL; > + > /* These list is for execution log. */ > static struct record_entry record_first; > static struct record_entry *record_list = &record_first; > @@ -94,6 +115,7 @@ static int record_insn_num = 0; > > /* The target_ops of process record. */ > static struct target_ops record_ops; > +static struct target_ops record_core_ops; > > /* The beneath function pointers. */ > static struct target_ops *record_beneath_to_resume_ops; > @@ -169,7 +191,7 @@ record_list_release_next (void) > } > > static void > -record_list_release_first (void) > +record_list_release_first_insn (void) > { > struct record_entry *tmp = NULL; > enum record_type type; > @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal) > if (q) > record_stop_at_limit = 0; > else > - error (_("Process record: inferior program stopped.")); > + error (_("Process record: stoped by user.")); > } > } > } > } > > +static void > +record_arch_list_cleanups (void *ignore) > +{ > + record_list_release (record_arch_list_tail); > +} > + > /* Before inferior step (when GDB record the running message, inferior > only can step), GDB will call this function to record the values to > record_list. This function will call gdbarch_process_record to > record the running message of inferior and set them to > record_arch_list, and add it to record_list. */ > > -static void > -record_message_cleanups (void *ignore) > -{ > - record_list_release (record_arch_list_tail); > -} > - > static int > record_message (void *args) > { > int ret; > struct regcache *regcache = args; > - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); > + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); > > record_arch_list_head = NULL; > record_arch_list_tail = NULL; > @@ -386,7 +408,7 @@ record_message (void *args) > record_list = record_arch_list_tail; > > if (record_insn_num == record_insn_max_num && record_insn_max_num) > - record_list_release_first (); > + record_list_release_first_insn (); > else > record_insn_num++; > > @@ -416,13 +438,291 @@ record_gdb_operation_disable_set (void) > return old_cleanups; > } > > +static inline void > +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, > + struct record_entry *entry) > +{ > + switch (entry->type) > + { > + case record_reg: /* reg */ > + { > + gdb_byte reg[MAX_REGISTER_SIZE]; > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_reg %s to " > + "inferior num = %d.\n", > + host_address_to_string (entry), > + entry->u.reg.num); > + > + regcache_cooked_read (regcache, entry->u.reg.num, reg); > + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); > + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); > + } > + break; > + > + case record_mem: /* mem */ > + { > + if (!record_list->u.mem.mem_entry_not_accessible) > + { > + gdb_byte *mem = alloca (entry->u.mem.len); > + > + if (record_debug > 1) > + fprintf_unfiltered (gdb_stdlog, > + "Process record: record_mem %s to " > + "inferior addr = %s len = %d.\n", > + host_address_to_string (entry), > + paddress (gdbarch, entry->u.mem.addr), > + record_list->u.mem.len); > + > + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) > + { > + record_list->u.mem.mem_entry_not_accessible = 1; > + if (record_debug) > + warning (_("Process record: error reading memory at " > + "addr = %s len = %d."), > + paddress (gdbarch, entry->u.mem.addr), > + entry->u.mem.len); > + } > + else > + { > + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, > + entry->u.mem.len)) > + { > + record_list->u.mem.mem_entry_not_accessible = 1; > + if (record_debug) > + warning (_("Process record: error writing memory at " > + "addr = %s len = %d."), > + paddress (gdbarch, entry->u.mem.addr), > + entry->u.mem.len); > + } > + else > + memcpy (entry->u.mem.val, mem, entry->u.mem.len); > + } > + } > + } > + break; > + } > +} > + > +static inline void > +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) > +{ > + if (read (fildes, buf, nbyte) != nbyte) > + error (_("Failed to read dump of execution records in '%s'."), > + recfilename); > +} > + > static void > -record_open (char *name, int from_tty) > +record_fd_cleanups (void *recfdp) > { > - struct target_ops *t; > + int recfd = *(int *) recfdp; > + close (recfd); > +} > > - if (record_debug) > - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); > +/* Load the execution log from a file. */ > + > +static void > +record_load (char *name) > +{ > + int recfd; > + uint32_t magic; > + struct cleanup *old_cleanups; > + struct cleanup *old_cleanups2; > + struct record_entry *rec; > + int insn_number = 0; > + > + if (!name || (name && !*name)) > + return; > + > + /* Open the load file. */ > + recfd = open (name, O_RDONLY | O_BINARY); > + if (recfd < 0) > + error (_("Failed to open '%s' for loading execution records: %s"), > + name, strerror (errno)); > + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); > + > + /* Check the magic code. */ > + record_read_dump (name, recfd, &magic, 4); > + if (magic != RECORD_FILE_MAGIC) > + error (_("'%s' is not a valid dump of execution records."), name); > + > + /* Load the entries in recfd to the record_arch_list_head and > + record_arch_list_tail. */ > + record_arch_list_head = NULL; > + record_arch_list_tail = NULL; > + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); > + > + while (1) > + { > + int ret; > + uint8_t tmpu8; > + uint64_t tmpu64; > + > + ret = read (recfd, &tmpu8, 1); > + if (ret < 0) > + error (_("Failed to read dump of execution records in '%s'."), name); > + if (ret == 0) > + break; > + > + switch (tmpu8) > + { > + case record_reg: /* reg */ > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_reg; > + > + /* Get num. */ > + record_read_dump (name, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.reg.num = tmpu64; > + > + /* Get val. */ > + record_read_dump (name, recfd, rec->u.reg.val, MAX_REGISTER_SIZE); > + > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Reading register %d (1 plus 8 plus %d bytes)\n"), > + rec->u.reg.num, > + MAX_REGISTER_SIZE); > + > + record_arch_list_add (rec); > + break; > + > + case record_mem: /* mem */ > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_mem; > + > + /* Get addr. */ > + record_read_dump (name, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.mem.addr = tmpu64; > + > + /* Get len. */ > + record_read_dump (name, recfd, &tmpu64, 8); > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + rec->u.mem.len = tmpu64; > + rec->u.mem.mem_entry_not_accessible = 0; > + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); > + > + /* Get val. */ > + record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len); > + > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), > + paddress (get_current_arch (), > + rec->u.mem.addr), > + rec->u.mem.len); > + > + record_arch_list_add (rec); > + break; > + > + case record_end: /* end */ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + _("Reading record_end (1 byte)\n")); > + > + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); > + rec->prev = NULL; > + rec->next = NULL; > + rec->type = record_end; > + record_arch_list_add (rec); > + insn_number ++; > + break; > + > + default: > + error (_("Format of '%s' is not right."), name); > + break; > + } > + } > + > + discard_cleanups (old_cleanups2); > + > + /* Add record_arch_list_head to the end of record list. */ > + for (rec = record_list; rec->next; rec = rec->next); > + rec->next = record_arch_list_head; > + record_arch_list_head->prev = rec; > + > + /* Update record_insn_num and record_insn_max_num. */ > + record_insn_num += insn_number; > + if (record_insn_num > record_insn_max_num) > + { > + record_insn_max_num = record_insn_num; > + warning (_("Auto increase record/replay buffer limit to %d."), > + record_insn_max_num); > + } > + > + do_cleanups (old_cleanups); > + > + /* Succeeded. */ > + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name); > +} > + > +static struct target_ops *tmp_to_resume_ops; > +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, > + enum target_signal); > +static struct target_ops *tmp_to_wait_ops; > +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, > + struct target_waitstatus *, > + int); > +static struct target_ops *tmp_to_store_registers_ops; > +static void (*tmp_to_store_registers) (struct target_ops *, > + struct regcache *, > + int regno); > +static struct target_ops *tmp_to_xfer_partial_ops; > +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, > + enum target_object object, > + const char *annex, > + gdb_byte *readbuf, > + const gdb_byte *writebuf, > + ULONGEST offset, > + LONGEST len); > +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, > + struct bp_target_info *); > +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, > + struct bp_target_info *); > + > +static void > +record_core_open_1 (char *name, int from_tty) > +{ > + struct regcache *regcache = get_current_regcache (); > + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); > + int i; > + > + if (!name || (name && !*name)) > + error (_("Argument for gdb record filename required.\n")); > + > + /* Get record_core_regbuf. */ > + target_fetch_registers (regcache, -1); > + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); > + for (i = 0; i < regnum; i ++) > + regcache_raw_collect (regcache, i, > + record_core_regbuf + MAX_REGISTER_SIZE * i); > + > + /* Get record_core_start and record_core_end. */ > + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) > + { > + xfree (record_core_regbuf); > + record_core_regbuf = NULL; > + error (_("\"%s\": Can't find sections: %s"), > + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); > + } > + > + push_target (&record_core_ops); > +} > + > +static void > +record_open_1 (char *name, int from_tty) > +{ > + struct target_ops *t; > > /* check exec */ > if (!target_has_execution) > @@ -438,6 +738,28 @@ record_open (char *name, int from_tty) > error (_("Process record: the current architecture doesn't support " > "record function.")); > > + if (!tmp_to_resume) > + error (_("Process record can't get to_resume.")); > + if (!tmp_to_wait) > + error (_("Process record can't get to_wait.")); > + if (!tmp_to_store_registers) > + error (_("Process record can't get to_store_registers.")); > + if (!tmp_to_insert_breakpoint) > + error (_("Process record can't get to_insert_breakpoint.")); > + if (!tmp_to_remove_breakpoint) > + error (_("Process record can't get to_remove_breakpoint.")); > + > + push_target (&record_ops); > +} > + > +static void > +record_open (char *name, int from_tty) > +{ > + struct target_ops *t; > + > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); > + > /* Check if record target is already running. */ > if (current_target.to_stratum == record_stratum) > { > @@ -447,70 +769,102 @@ record_open (char *name, int from_tty) > 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; > + /* Reset the tmp beneath pointers. */ > + tmp_to_resume_ops = NULL; > + tmp_to_resume = NULL; > + tmp_to_wait_ops = NULL; > + tmp_to_wait = NULL; > + tmp_to_store_registers_ops = NULL; > + tmp_to_store_registers = NULL; > + tmp_to_xfer_partial_ops = NULL; > + tmp_to_xfer_partial = NULL; > + tmp_to_insert_breakpoint = NULL; > + tmp_to_remove_breakpoint = NULL; > > /* Set the beneath function pointers. */ > for (t = current_target.beneath; t != NULL; t = t->beneath) > { > - if (!record_beneath_to_resume) > + if (!tmp_to_resume) > { > - record_beneath_to_resume = t->to_resume; > - record_beneath_to_resume_ops = t; > + tmp_to_resume = t->to_resume; > + tmp_to_resume_ops = t; > } > - if (!record_beneath_to_wait) > + if (!tmp_to_wait) > { > - record_beneath_to_wait = t->to_wait; > - record_beneath_to_wait_ops = t; > + tmp_to_wait = t->to_wait; > + tmp_to_wait_ops = t; > } > - if (!record_beneath_to_store_registers) > + if (!tmp_to_store_registers) > { > - record_beneath_to_store_registers = t->to_store_registers; > - record_beneath_to_store_registers_ops = t; > + tmp_to_store_registers = t->to_store_registers; > + tmp_to_store_registers_ops = t; > } > - if (!record_beneath_to_xfer_partial) > + if (!tmp_to_xfer_partial) > { > - record_beneath_to_xfer_partial = t->to_xfer_partial; > - record_beneath_to_xfer_partial_ops = t; > + tmp_to_xfer_partial = t->to_xfer_partial; > + tmp_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 (!tmp_to_insert_breakpoint) > + tmp_to_insert_breakpoint = t->to_insert_breakpoint; > + if (!tmp_to_remove_breakpoint) > + tmp_to_remove_breakpoint = t->to_remove_breakpoint; > } > - if (!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) > + if (!tmp_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); > + if (current_target.to_stratum == core_stratum) > + record_core_open_1 (name, from_tty); > + else > + record_open_1 (name, from_tty); > > /* Reset */ > record_insn_num = 0; > record_list = &record_first; > record_list->next = NULL; > + > + /* Set the tmp beneath pointers to beneath pointers. */ > + record_beneath_to_resume_ops = tmp_to_resume_ops; > + record_beneath_to_resume = tmp_to_resume; > + record_beneath_to_wait_ops = tmp_to_wait_ops; > + record_beneath_to_wait = tmp_to_wait; > + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; > + record_beneath_to_store_registers = tmp_to_store_registers; > + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; > + record_beneath_to_xfer_partial = tmp_to_xfer_partial; > + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; > + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; > + > + /* Load if there is argument. */ > + record_load (name); > } > > static void > record_close (int quitting) > { > + struct record_core_buf_entry *entry; > + > if (record_debug) > fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); > > record_list_release (record_list); > + > + /* Release record_core_regbuf. */ > + if (record_core_regbuf) > + { > + xfree (record_core_regbuf); > + record_core_regbuf = NULL; > + } > + > + /* Release record_core_buf_list. */ > + if (record_core_buf_list) > + { > + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) > + { > + xfree (record_core_buf_list); > + record_core_buf_list = entry; > + } > + record_core_buf_list = NULL; > + } > } > > static int record_resume_step = 0; > @@ -584,7 +938,7 @@ record_wait (struct target_ops *ops, > "record_resume_step = %d\n", > record_resume_step); > > - if (!RECORD_IS_REPLAY) > + if (!RECORD_IS_REPLAY && ops != &record_core_ops) > { > if (record_resume_error) > { > @@ -712,76 +1066,9 @@ record_wait (struct target_ops *ops, > 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 */ > - /* Nothing to do if the entry is flagged not_accessible. */ > - if (!record_list->u.mem.mem_entry_not_accessible) > - { > - 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 = %s len = %d.\n", > - host_address_to_string (record_list), > - paddress (gdbarch, > - record_list->u.mem.addr), > - record_list->u.mem.len); > + record_exec_entry (regcache, gdbarch, record_list); > > - if (target_read_memory (record_list->u.mem.addr, mem, > - record_list->u.mem.len)) > - { > - if (execution_direction != EXEC_REVERSE) > - error (_("Process record: error reading memory at " > - "addr = %s len = %d."), > - paddress (gdbarch, record_list->u.mem.addr), > - record_list->u.mem.len); > - else > - /* Read failed -- > - flag entry as not_accessible. */ > - record_list->u.mem.mem_entry_not_accessible = 1; > - } > - else > - { > - if (target_write_memory (record_list->u.mem.addr, > - record_list->u.mem.val, > - record_list->u.mem.len)) > - { > - if (execution_direction != EXEC_REVERSE) > - error (_("Process record: error writing memory at " > - "addr = %s len = %d."), > - paddress (gdbarch, record_list->u.mem.addr), > - record_list->u.mem.len); > - else > - /* Write failed -- > - flag entry as not_accessible. */ > - record_list->u.mem.mem_entry_not_accessible = 1; > - } > - else > - { > - memcpy (record_list->u.mem.val, mem, > - record_list->u.mem.len); > - } > - } > - } > - } > - else > + if (record_list->type == record_end) > { > if (record_debug > 1) > fprintf_unfiltered (gdb_stdlog, > @@ -901,6 +1188,7 @@ record_kill (struct target_ops *ops) > fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); > > unpush_target (&record_ops); > + > target_kill (); > } > > @@ -945,7 +1233,7 @@ record_registers_change (struct regcache > record_list = record_arch_list_tail; > > if (record_insn_num == record_insn_max_num && record_insn_max_num) > - record_list_release_first (); > + record_list_release_first_insn (); > else > record_insn_num++; > } > @@ -1058,7 +1346,7 @@ record_xfer_partial (struct target_ops * > record_list = record_arch_list_tail; > > if (record_insn_num == record_insn_max_num && record_insn_max_num) > - record_list_release_first (); > + record_list_release_first_insn (); > else > record_insn_num++; > } > @@ -1138,6 +1426,191 @@ init_record_ops (void) > } > > static void > +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, > + enum target_signal siggnal) > +{ > + record_resume_step = step; > + record_resume_siggnal = siggnal; > +} > + > +static void > +record_core_kill (struct target_ops *ops) > +{ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); > + > + unpush_target (&record_core_ops); > +} > + > +static void > +record_core_fetch_registers (struct target_ops *ops, > + struct regcache *regcache, > + int regno) > +{ > + if (regno < 0) > + { > + int num = gdbarch_num_regs (get_regcache_arch (regcache)); > + int i; > + > + for (i = 0; i < num; i ++) > + regcache_raw_supply (regcache, i, > + record_core_regbuf + MAX_REGISTER_SIZE * i); > + } > + else > + regcache_raw_supply (regcache, regno, > + record_core_regbuf + MAX_REGISTER_SIZE * regno); > +} > + > +static void > +record_core_prepare_to_store (struct regcache *regcache) > +{ > +} > + > +static void > +record_core_store_registers (struct target_ops *ops, > + struct regcache *regcache, > + int regno) > +{ > + if (record_gdb_operation_disable) > + regcache_raw_collect (regcache, regno, > + record_core_regbuf + MAX_REGISTER_SIZE * regno); > + else > + error (_("You can't do that without a process to debug.")); > +} > + > +static LONGEST > +record_core_xfer_partial (struct target_ops *ops, enum target_object object, > + const char *annex, gdb_byte *readbuf, > + const gdb_byte *writebuf, ULONGEST offset, > + LONGEST len) > +{ > + if (object == TARGET_OBJECT_MEMORY) > + { > + if (record_gdb_operation_disable || !writebuf) > + { > + struct target_section *p; > + for (p = record_core_start; p < record_core_end; p++) > + { > + if (offset >= p->addr) > + { > + struct record_core_buf_entry *entry; > + > + if (offset >= p->endaddr) > + continue; > + > + if (offset + len > p->endaddr) > + len = p->endaddr - offset; > + > + offset -= p->addr; > + > + /* Read readbuf or write writebuf p, offset, len. */ > + /* Check flags. */ > + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR > + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) > + { > + if (readbuf) > + memset (readbuf, 0, len); > + return len; > + } > + /* Get record_core_buf_entry. */ > + for (entry = record_core_buf_list; entry; > + entry = entry->prev) > + if (entry->p == p) > + break; > + if (writebuf) > + { > + if (!entry) > + { > + /* Add a new entry. */ > + entry > + = (struct record_core_buf_entry *) > + xmalloc > + (sizeof (struct record_core_buf_entry)); > + entry->p = p; > + if (!bfd_malloc_and_get_section (p->bfd, > + p->the_bfd_section, > + &entry->buf)) > + { > + xfree (entry); > + return 0; > + } > + entry->prev = record_core_buf_list; > + record_core_buf_list = entry; > + } > + > + memcpy (entry->buf + offset, writebuf, (size_t) len); > + } > + else > + { > + if (!entry) > + return record_beneath_to_xfer_partial > + (record_beneath_to_xfer_partial_ops, > + object, annex, readbuf, writebuf, > + offset, len); > + > + memcpy (readbuf, entry->buf + offset, (size_t) len); > + } > + > + return len; > + } > + } > + > + return -1; > + } > + else > + error (_("You can't do that without a process to debug.")); > + } > + > + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, > + object, annex, readbuf, writebuf, > + offset, len); > +} > + > +static int > +record_core_insert_breakpoint (struct gdbarch *gdbarch, > + struct bp_target_info *bp_tgt) > +{ > + return 0; > +} > + > +static int > +record_core_remove_breakpoint (struct gdbarch *gdbarch, > + struct bp_target_info *bp_tgt) > +{ > + return 0; > +} > + > +int > +record_core_has_execution (struct target_ops *ops) > +{ > + return 1; > +} > + > +static void > +init_record_core_ops (void) > +{ > + record_core_ops.to_shortname = "record_core"; > + record_core_ops.to_longname = "Process record and replay target"; > + record_core_ops.to_doc = > + "Log program while executing and replay execution from log."; > + record_core_ops.to_open = record_open; > + record_core_ops.to_close = record_close; > + record_core_ops.to_resume = record_core_resume; > + record_core_ops.to_wait = record_wait; > + record_core_ops.to_kill = record_core_kill; > + record_core_ops.to_fetch_registers = record_core_fetch_registers; > + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; > + record_core_ops.to_store_registers = record_core_store_registers; > + record_core_ops.to_xfer_partial = record_core_xfer_partial; > + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; > + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; > + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; > + record_core_ops.to_has_execution = record_core_has_execution; > + record_core_ops.to_stratum = record_stratum; > + record_core_ops.to_magic = OPS_MAGIC; > +} > + > +static void > show_record_debug (struct ui_file *file, int from_tty, > struct cmd_list_element *c, const char *value) > { > @@ -1153,6 +1626,212 @@ cmd_record_start (char *args, int from_t > execute_command ("target record", from_tty); > } > > +static void > +cmd_record_load (char *args, int from_tty) > +{ > + char buf[512]; > + > + snprintf (buf, 512, "target record %s", args); > + execute_command (buf, from_tty); > +} > + > +static inline void > +record_write_dump (char *recfilename, int fildes, const void *buf, > + size_t nbyte) > +{ > + if (write (fildes, buf, nbyte) != nbyte) > + error (_("Failed to write dump of execution records to '%s'."), > + recfilename); > +} > + > +/* Record log save-file format > + Version 1 > + > + Header: > + 4 bytes: magic number htonl(0x20090726). > + NOTE: be sure to change whenever this file format changes! > + > + Records: > + record_end: > + 1 byte: record type (record_end). > + record_reg: > + 1 byte: record type (record_reg). > + 8 bytes: register id (network byte order). > + MAX_REGISTER_SIZE bytes: register value. > + record_mem: > + 1 byte: record type (record_mem). > + 8 bytes: memory address (network byte order). > + 8 bytes: memory length (network byte order). > + n bytes: memory value (n == memory length). > +*/ > + > +/* Dump the execution log to a file. */ > + > +static void > +cmd_record_dump (char *args, int from_tty) > +{ > + char *recfilename, recfilename_buffer[40]; > + int recfd; > + struct record_entry *cur_record_list; > + uint32_t magic; > + struct regcache *regcache; > + struct gdbarch *gdbarch; > + struct cleanup *old_cleanups; > + struct cleanup *set_cleanups; > + > + if (strcmp (current_target.to_shortname, "record") != 0) > + error (_("Process record is not started.\n")); > + > + if (args && *args) > + recfilename = args; > + else > + { > + /* Default corefile name is "gdb_record.PID". */ > + snprintf (recfilename_buffer, 40, "gdb_record.%d", > + PIDGET (inferior_ptid)); > + recfilename = recfilename_buffer; > + } > + > + /* Open the dump file. */ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + _("Saving recording to file '%s'\n"), > + recfilename); > + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, > + S_IRUSR | S_IWUSR); > + if (recfd < 0) > + error (_("Failed to open '%s' for dump execution records: %s"), > + recfilename, strerror (errno)); > + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); > + > + /* Save the current record entry to "cur_record_list". */ > + cur_record_list = record_list; > + > + /* Get the values of regcache and gdbarch. */ > + regcache = get_current_regcache (); > + gdbarch = get_regcache_arch (regcache); > + > + /* Disable the GDB operation record. */ > + set_cleanups = record_gdb_operation_disable_set (); > + > + /* Write the magic code. */ > + magic = RECORD_FILE_MAGIC; > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), > + magic); > + record_write_dump (recfilename, recfd, &magic, 4); > + > + /* Reverse execute to the begin of record list. */ > + while (1) > + { > + /* Check for beginning and end of log. */ > + if (record_list == &record_first) > + break; > + > + record_exec_entry (regcache, gdbarch, record_list); > + > + if (record_list->prev) > + record_list = record_list->prev; > + } > + > + /* Dump the entries to recfd and forward execute to the end of > + record list. */ > + while (1) > + { > + /* Dump entry. */ > + if (record_list != &record_first) > + { > + uint8_t tmpu8; > + uint64_t tmpu64; > + > + tmpu8 = record_list->type; > + record_write_dump (recfilename, recfd, &tmpu8, 1); > + > + switch (record_list->type) > + { > + case record_reg: /* reg */ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Writing register %d (1 plus 8 plus %d bytes)\n"), > + record_list->u.reg.num, > + MAX_REGISTER_SIZE); > + > + tmpu64 = record_list->u.reg.num; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + record_write_dump (recfilename, recfd, record_list->u.reg.val, > + MAX_REGISTER_SIZE); > + break; > + > + case record_mem: /* mem */ > + if (!record_list->u.mem.mem_entry_not_accessible) > + { > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, _("\ > +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), > + paddress (gdbarch, > + record_list->u.mem.addr), > + record_list->u.mem.len); > + > + tmpu64 = record_list->u.mem.addr; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + tmpu64 = record_list->u.mem.len; > + if (BYTE_ORDER == LITTLE_ENDIAN) > + tmpu64 = bswap_64 (tmpu64); > + record_write_dump (recfilename, recfd, &tmpu64, 8); > + > + record_write_dump (recfilename, recfd, > + record_list->u.mem.val, > + record_list->u.mem.len); > + } > + break; > + > + case record_end: > + /* FIXME: record the contents of record_end rec. */ > + if (record_debug) > + fprintf_unfiltered (gdb_stdlog, > + _("Writing record_end (1 byte)\n")); > + break; > + } > + } > + > + /* Execute entry. */ > + record_exec_entry (regcache, gdbarch, record_list); > + > + if (record_list->next) > + record_list = record_list->next; > + else > + break; > + } > + > + /* Reverse execute to cur_record_list. */ > + while (1) > + { > + /* Check for beginning and end of log. */ > + if (record_list == cur_record_list) > + break; > + > + record_exec_entry (regcache, gdbarch, record_list); > + > + if (record_list->prev) > + record_list = record_list->prev; > + } > + > + do_cleanups (set_cleanups); > + do_cleanups (old_cleanups); > + > + /* Succeeded. */ > + fprintf_filtered (gdb_stdout, _("Saved dump of execution " > + "records to `%s'.\n"), > + recfilename); > +} > + > /* Truncate the record log from the present point > of replay until the end. */ > > @@ -1185,7 +1864,12 @@ cmd_record_stop (char *args, int from_tt > { > if (!record_list || !from_tty || query (_("Delete recorded log and " > "stop recording?"))) > - unpush_target (&record_ops); > + { > + if (strcmp (current_target.to_shortname, "record") == 0) > + unpush_target (&record_ops); > + else > + unpush_target (&record_core_ops); > + } > } > else > printf_unfiltered (_("Process record is not started.\n")); > @@ -1203,7 +1887,7 @@ set_record_insn_max_num (char *args, int > "the first ones?\n")); > > while (record_insn_num > record_insn_max_num) > - record_list_release_first (); > + record_list_release_first_insn (); > } > } > > @@ -1243,6 +1927,8 @@ info_record_command (char *args, int fro > void > _initialize_record (void) > { > + struct cmd_list_element *c; > + > /* Init record_first. */ > record_first.prev = NULL; > record_first.next = NULL; > @@ -1250,6 +1936,8 @@ _initialize_record (void) > > init_record_ops (); > add_target (&record_ops); > + init_record_core_ops (); > + add_target (&record_core_ops); > > add_setshow_zinteger_cmd ("record", no_class, &record_debug, > _("Set debugging of record/replay feature."), > @@ -1259,9 +1947,10 @@ _initialize_record (void) > NULL, show_record_debug, &setdebuglist, > &showdebuglist); > > - add_prefix_cmd ("record", class_obscure, cmd_record_start, > - _("Abbreviated form of \"target record\" command."), > - &record_cmdlist, "record ", 0, &cmdlist); > + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, > + _("Abbreviated form of \"target record\" command."), > + &record_cmdlist, "record ", 0, &cmdlist); > + set_cmd_completer (c, filename_completer); > add_com_alias ("rec", "record", class_obscure, 1); > add_prefix_cmd ("record", class_support, set_record_command, > _("Set record options"), &set_record_cmdlist, > @@ -1276,6 +1965,16 @@ _initialize_record (void) > "info record ", 0, &infolist); > add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); > > + c = add_cmd ("load", class_obscure, cmd_record_load, > + _("Load previously dumped execution records from \ > +a file given as argument."), > + &record_cmdlist); > + set_cmd_completer (c, filename_completer); > + c = add_cmd ("dump", class_obscure, cmd_record_dump, > + _("Dump the execution records to a file.\n\ > +Argument is optional filename. Default filename is > 'gdb_record.<process_id>'."), > + &record_cmdlist); > + set_cmd_completer (c, filename_completer); > > add_cmd ("delete", class_obscure, cmd_record_delete, > _("Delete the rest of execution log and start recording it anew."), ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-24 18:32 ` Michael Snyder @ 2009-08-25 8:47 ` Hui Zhu 2009-08-26 1:40 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-25 8:47 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches On Tue, Aug 25, 2009 at 01:51, Michael Snyder<msnyder@vmware.com> wrote: > Hui Zhu wrote: >> >> On Mon, Aug 24, 2009 at 07:21, Michael Snyder<msnyder@vmware.com> wrote: >>> >>> Hui Zhu wrote: >>> >>>> Hi Michael, >>>> >>>> I make a new version patch. It has a lot of changes. >>>> Remove record_core and add a new target record_core for core target to >>>> make the code more clear. >>>> Make the load together with record_open. >>>> >>>> Please help me review it. >>> >>> Hi Hui, >>> >>> In this review, I'm going to comment only on the parts of the >>> patch that relate to the record_core_ops (ie. pushing the >>> record stratum on top of the core file stratum). >>> >>> Those parts of the patch are much improved. I like this >>> version a lot better. Thanks for reworking it. >>> >> >> >> Hi Michael, >> >> Thanks for your review. I make a new patch that update the memset code >> to: > > Way cool, thanks! > > Now I can comment on the dump/load part of the patch. > It does not seem as if previous issues have been addressed: > 1) Core file and record file are separate, no way to verify > that they correspond to each other. > 2) Previous log not cleared when loading a new log. For it, I think we don't need worry about it. Because I make the record load together with record_open. So each time when we load record, the record list is empty. > > Have you considered combining this patch with the idea that I > proposed -- storing the record log in the core file? Seems like > that would be very easy now -- gdb already has the core file open, > and there is a global pointer to the BFD. > See gdbcore.h:extern bfd* core_bfd; Cool. If we just want record_core_ops support load, we can remove the cmd "record load". :) > > In fact, just for fun, I've merged the two patches in my > local tree. It seems to work quite well. I can just post > it here if you like, or here's a hint to show you how it goes: Why not? Please post it. I can not wait to try it. :) Thanks, Hui > > static void > record_load (void) > { > int recfd; > uint32_t magic; > struct cleanup *old_cleanups2; > struct record_entry *rec; > int insn_number = 0; > asection *osec; > void nullify_last_target_wait_ptid (void); > > /* We load the execution log from the open core bfd, > if there is one. */ > if (core_bfd == NULL) > return; > > if (record_debug) > fprintf_filtered (gdb_stdlog, > _("Restoring recording from core file.\n")); > > /* Now need to find our special note section. */ > osec = bfd_get_section_by_name (core_bfd, "null0"); > > >> if (target_read_memory (entry->u.mem.addr, mem, >> entry->u.mem.len)) >> { >> record_list->u.mem.mem_entry_not_accessible = 1; >> if (record_debug) >> warning (_("Process record: error reading memory at " >> "addr = %s len = %d."), >> paddress (gdbarch, entry->u.mem.addr), >> entry->u.mem.len); >> } >> else >> { >> if (target_write_memory (entry->u.mem.addr, >> entry->u.mem.val, >> entry->u.mem.len)) >> { >> record_list->u.mem.mem_entry_not_accessible = 1; >> if (record_debug) >> warning (_("Process record: error writing memory at " >> "addr = %s len = %d."), >> paddress (gdbarch, entry->u.mem.addr), >> entry->u.mem.len); >> } >> else >> memcpy (entry->u.mem.val, mem, entry->u.mem.len); >> } >> >> Please help me review it. >> >> Thanks, >> Hui >> >> --- >> record.c | 951 >> ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- >> 1 file changed, 825 insertions(+), 126 deletions(-) >> >> --- a/record.c >> +++ b/record.c >> @@ -23,15 +23,23 @@ >> #include "gdbthread.h" >> #include "event-top.h" >> #include "exceptions.h" >> +#include "completer.h" >> +#include "arch-utils.h" >> +#include "gdbcore.h" >> +#include "exec.h" >> #include "record.h" >> >> +#include <byteswap.h> >> #include <signal.h> >> +#include <netinet/in.h> >> >> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 >> >> #define RECORD_IS_REPLAY \ >> (record_list->next || execution_direction == EXEC_REVERSE) >> >> +#define RECORD_FILE_MAGIC htonl(0x20090726) >> + >> /* These are the core struct of record function. >> >> An record_entry is a record of the value change of a register >> @@ -78,9 +86,22 @@ struct record_entry >> } u; >> }; >> >> +struct record_core_buf_entry >> +{ >> + struct record_core_buf_entry *prev; >> + struct target_section *p; >> + bfd_byte *buf; >> +}; >> + >> /* This is the debug switch for process record. */ >> int record_debug = 0; >> >> +/* Record buf with core target. */ >> +static gdb_byte *record_core_regbuf = NULL; >> +static struct target_section *record_core_start; >> +static struct target_section *record_core_end; >> +static struct record_core_buf_entry *record_core_buf_list = NULL; >> + >> /* These list is for execution log. */ >> static struct record_entry record_first; >> static struct record_entry *record_list = &record_first; >> @@ -94,6 +115,7 @@ static int record_insn_num = 0; >> >> /* The target_ops of process record. */ >> static struct target_ops record_ops; >> +static struct target_ops record_core_ops; >> >> /* The beneath function pointers. */ >> static struct target_ops *record_beneath_to_resume_ops; >> @@ -169,7 +191,7 @@ record_list_release_next (void) >> } >> >> static void >> -record_list_release_first (void) >> +record_list_release_first_insn (void) >> { >> struct record_entry *tmp = NULL; >> enum record_type type; >> @@ -340,30 +362,30 @@ record_check_insn_num (int set_terminal) >> if (q) >> record_stop_at_limit = 0; >> else >> - error (_("Process record: inferior program stopped.")); >> + error (_("Process record: stoped by user.")); >> } >> } >> } >> } >> >> +static void >> +record_arch_list_cleanups (void *ignore) >> +{ >> + record_list_release (record_arch_list_tail); >> +} >> + >> /* Before inferior step (when GDB record the running message, inferior >> only can step), GDB will call this function to record the values to >> record_list. This function will call gdbarch_process_record to >> record the running message of inferior and set them to >> record_arch_list, and add it to record_list. */ >> >> -static void >> -record_message_cleanups (void *ignore) >> -{ >> - record_list_release (record_arch_list_tail); >> -} >> - >> static int >> record_message (void *args) >> { >> int ret; >> struct regcache *regcache = args; >> - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, >> 0); >> + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, >> 0); >> >> record_arch_list_head = NULL; >> record_arch_list_tail = NULL; >> @@ -386,7 +408,7 @@ record_message (void *args) >> record_list = record_arch_list_tail; >> >> if (record_insn_num == record_insn_max_num && record_insn_max_num) >> - record_list_release_first (); >> + record_list_release_first_insn (); >> else >> record_insn_num++; >> >> @@ -416,13 +438,291 @@ record_gdb_operation_disable_set (void) >> return old_cleanups; >> } >> >> +static inline void >> +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, >> + struct record_entry *entry) >> +{ >> + switch (entry->type) >> + { >> + case record_reg: /* reg */ >> + { >> + gdb_byte reg[MAX_REGISTER_SIZE]; >> + >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_reg %s to " >> + "inferior num = %d.\n", >> + host_address_to_string (entry), >> + entry->u.reg.num); >> + >> + regcache_cooked_read (regcache, entry->u.reg.num, reg); >> + regcache_cooked_write (regcache, entry->u.reg.num, >> entry->u.reg.val); >> + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); >> + } >> + break; >> + >> + case record_mem: /* mem */ >> + { >> + if (!record_list->u.mem.mem_entry_not_accessible) >> + { >> + gdb_byte *mem = alloca (entry->u.mem.len); >> + >> + if (record_debug > 1) >> + fprintf_unfiltered (gdb_stdlog, >> + "Process record: record_mem %s to " >> + "inferior addr = %s len = %d.\n", >> + host_address_to_string (entry), >> + paddress (gdbarch, entry->u.mem.addr), >> + record_list->u.mem.len); >> + >> + if (target_read_memory (entry->u.mem.addr, mem, >> entry->u.mem.len)) >> + { >> + record_list->u.mem.mem_entry_not_accessible = 1; >> + if (record_debug) >> + warning (_("Process record: error reading memory at " >> + "addr = %s len = %d."), >> + paddress (gdbarch, entry->u.mem.addr), >> + entry->u.mem.len); >> + } >> + else >> + { >> + if (target_write_memory (entry->u.mem.addr, >> entry->u.mem.val, >> + entry->u.mem.len)) >> + { >> + record_list->u.mem.mem_entry_not_accessible = 1; >> + if (record_debug) >> + warning (_("Process record: error writing memory at >> " >> + "addr = %s len = %d."), >> + paddress (gdbarch, entry->u.mem.addr), >> + entry->u.mem.len); >> + } >> + else >> + memcpy (entry->u.mem.val, mem, entry->u.mem.len); >> + } >> + } >> + } >> + break; >> + } >> +} >> + >> +static inline void >> +record_read_dump (char *recfilename, int fildes, void *buf, size_t nbyte) >> +{ >> + if (read (fildes, buf, nbyte) != nbyte) >> + error (_("Failed to read dump of execution records in '%s'."), >> + recfilename); >> +} >> + >> static void >> -record_open (char *name, int from_tty) >> +record_fd_cleanups (void *recfdp) >> { >> - struct target_ops *t; >> + int recfd = *(int *) recfdp; >> + close (recfd); >> +} >> >> - if (record_debug) >> - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); >> +/* Load the execution log from a file. */ >> + >> +static void >> +record_load (char *name) >> +{ >> + int recfd; >> + uint32_t magic; >> + struct cleanup *old_cleanups; >> + struct cleanup *old_cleanups2; >> + struct record_entry *rec; >> + int insn_number = 0; >> + >> + if (!name || (name && !*name)) >> + return; >> + >> + /* Open the load file. */ >> + recfd = open (name, O_RDONLY | O_BINARY); >> + if (recfd < 0) >> + error (_("Failed to open '%s' for loading execution records: %s"), >> + name, strerror (errno)); >> + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); >> + >> + /* Check the magic code. */ >> + record_read_dump (name, recfd, &magic, 4); >> + if (magic != RECORD_FILE_MAGIC) >> + error (_("'%s' is not a valid dump of execution records."), name); >> + >> + /* Load the entries in recfd to the record_arch_list_head and >> + record_arch_list_tail. */ >> + record_arch_list_head = NULL; >> + record_arch_list_tail = NULL; >> + old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); >> + >> + while (1) >> + { >> + int ret; >> + uint8_t tmpu8; >> + uint64_t tmpu64; >> + >> + ret = read (recfd, &tmpu8, 1); >> + if (ret < 0) >> + error (_("Failed to read dump of execution records in '%s'."), >> name); >> + if (ret == 0) >> + break; >> + >> + switch (tmpu8) >> + { >> + case record_reg: /* reg */ >> + rec = (struct record_entry *) xmalloc (sizeof (struct >> record_entry)); >> + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_reg; >> + >> + /* Get num. */ >> + record_read_dump (name, recfd, &tmpu64, 8); >> + if (BYTE_ORDER == LITTLE_ENDIAN) >> + tmpu64 = bswap_64 (tmpu64); >> + rec->u.reg.num = tmpu64; >> + >> + /* Get val. */ >> + record_read_dump (name, recfd, rec->u.reg.val, >> MAX_REGISTER_SIZE); >> + >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, _("\ >> +Reading register %d (1 plus 8 plus %d bytes)\n"), >> + rec->u.reg.num, >> + MAX_REGISTER_SIZE); >> + >> + record_arch_list_add (rec); >> + break; >> + >> + case record_mem: /* mem */ >> + rec = (struct record_entry *) xmalloc (sizeof (struct >> record_entry)); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_mem; >> + >> + /* Get addr. */ >> + record_read_dump (name, recfd, &tmpu64, 8); >> + if (BYTE_ORDER == LITTLE_ENDIAN) >> + tmpu64 = bswap_64 (tmpu64); >> + rec->u.mem.addr = tmpu64; >> + >> + /* Get len. */ >> + record_read_dump (name, recfd, &tmpu64, 8); >> + if (BYTE_ORDER == LITTLE_ENDIAN) >> + tmpu64 = bswap_64 (tmpu64); >> + rec->u.mem.len = tmpu64; >> + rec->u.mem.mem_entry_not_accessible = 0; >> + rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); >> + >> + /* Get val. */ >> + record_read_dump (name, recfd, rec->u.mem.val, rec->u.mem.len); >> + >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, _("\ >> +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), >> + paddress (get_current_arch (), >> + rec->u.mem.addr), >> + rec->u.mem.len); >> + >> + record_arch_list_add (rec); >> + break; >> + >> + case record_end: /* end */ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + _("Reading record_end (1 byte)\n")); >> + >> + rec = (struct record_entry *) xmalloc (sizeof (struct >> record_entry)); >> + rec->prev = NULL; >> + rec->next = NULL; >> + rec->type = record_end; >> + record_arch_list_add (rec); >> + insn_number ++; >> + break; >> + >> + default: >> + error (_("Format of '%s' is not right."), name); >> + break; >> + } >> + } >> + >> + discard_cleanups (old_cleanups2); >> + >> + /* Add record_arch_list_head to the end of record list. */ >> + for (rec = record_list; rec->next; rec = rec->next); >> + rec->next = record_arch_list_head; >> + record_arch_list_head->prev = rec; >> + >> + /* Update record_insn_num and record_insn_max_num. */ >> + record_insn_num += insn_number; >> + if (record_insn_num > record_insn_max_num) >> + { >> + record_insn_max_num = record_insn_num; >> + warning (_("Auto increase record/replay buffer limit to %d."), >> + record_insn_max_num); >> + } >> + >> + do_cleanups (old_cleanups); >> + >> + /* Succeeded. */ >> + fprintf_filtered (gdb_stdout, "Loaded recfile %s.\n", name); >> +} >> + >> +static struct target_ops *tmp_to_resume_ops; >> +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, >> + enum target_signal); >> +static struct target_ops *tmp_to_wait_ops; >> +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, >> + struct target_waitstatus *, >> + int); >> +static struct target_ops *tmp_to_store_registers_ops; >> +static void (*tmp_to_store_registers) (struct target_ops *, >> + struct regcache *, >> + int regno); >> +static struct target_ops *tmp_to_xfer_partial_ops; >> +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, >> + enum target_object object, >> + const char *annex, >> + gdb_byte *readbuf, >> + const gdb_byte *writebuf, >> + ULONGEST offset, >> + LONGEST len); >> +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, >> + struct bp_target_info *); >> +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, >> + struct bp_target_info *); >> + >> +static void >> +record_core_open_1 (char *name, int from_tty) >> +{ >> + struct regcache *regcache = get_current_regcache (); >> + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); >> + int i; >> + >> + if (!name || (name && !*name)) >> + error (_("Argument for gdb record filename required.\n")); >> + >> + /* Get record_core_regbuf. */ >> + target_fetch_registers (regcache, -1); >> + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); >> + for (i = 0; i < regnum; i ++) >> + regcache_raw_collect (regcache, i, >> + record_core_regbuf + MAX_REGISTER_SIZE * i); >> + >> + /* Get record_core_start and record_core_end. */ >> + if (build_section_table (core_bfd, &record_core_start, >> &record_core_end)) >> + { >> + xfree (record_core_regbuf); >> + record_core_regbuf = NULL; >> + error (_("\"%s\": Can't find sections: %s"), >> + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); >> + } >> + >> + push_target (&record_core_ops); >> +} >> + >> +static void >> +record_open_1 (char *name, int from_tty) >> +{ >> + struct target_ops *t; >> >> /* check exec */ >> if (!target_has_execution) >> @@ -438,6 +738,28 @@ record_open (char *name, int from_tty) >> error (_("Process record: the current architecture doesn't support " >> "record function.")); >> >> + if (!tmp_to_resume) >> + error (_("Process record can't get to_resume.")); >> + if (!tmp_to_wait) >> + error (_("Process record can't get to_wait.")); >> + if (!tmp_to_store_registers) >> + error (_("Process record can't get to_store_registers.")); >> + if (!tmp_to_insert_breakpoint) >> + error (_("Process record can't get to_insert_breakpoint.")); >> + if (!tmp_to_remove_breakpoint) >> + error (_("Process record can't get to_remove_breakpoint.")); >> + >> + push_target (&record_ops); >> +} >> + >> +static void >> +record_open (char *name, int from_tty) >> +{ >> + struct target_ops *t; >> + >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); >> + >> /* Check if record target is already running. */ >> if (current_target.to_stratum == record_stratum) >> { >> @@ -447,70 +769,102 @@ record_open (char *name, int from_tty) >> 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; >> + /* Reset the tmp beneath pointers. */ >> + tmp_to_resume_ops = NULL; >> + tmp_to_resume = NULL; >> + tmp_to_wait_ops = NULL; >> + tmp_to_wait = NULL; >> + tmp_to_store_registers_ops = NULL; >> + tmp_to_store_registers = NULL; >> + tmp_to_xfer_partial_ops = NULL; >> + tmp_to_xfer_partial = NULL; >> + tmp_to_insert_breakpoint = NULL; >> + tmp_to_remove_breakpoint = NULL; >> >> /* Set the beneath function pointers. */ >> for (t = current_target.beneath; t != NULL; t = t->beneath) >> { >> - if (!record_beneath_to_resume) >> + if (!tmp_to_resume) >> { >> - record_beneath_to_resume = t->to_resume; >> - record_beneath_to_resume_ops = t; >> + tmp_to_resume = t->to_resume; >> + tmp_to_resume_ops = t; >> } >> - if (!record_beneath_to_wait) >> + if (!tmp_to_wait) >> { >> - record_beneath_to_wait = t->to_wait; >> - record_beneath_to_wait_ops = t; >> + tmp_to_wait = t->to_wait; >> + tmp_to_wait_ops = t; >> } >> - if (!record_beneath_to_store_registers) >> + if (!tmp_to_store_registers) >> { >> - record_beneath_to_store_registers = t->to_store_registers; >> - record_beneath_to_store_registers_ops = t; >> + tmp_to_store_registers = t->to_store_registers; >> + tmp_to_store_registers_ops = t; >> } >> - if (!record_beneath_to_xfer_partial) >> + if (!tmp_to_xfer_partial) >> { >> - record_beneath_to_xfer_partial = t->to_xfer_partial; >> - record_beneath_to_xfer_partial_ops = t; >> + tmp_to_xfer_partial = t->to_xfer_partial; >> + tmp_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 (!tmp_to_insert_breakpoint) >> + tmp_to_insert_breakpoint = t->to_insert_breakpoint; >> + if (!tmp_to_remove_breakpoint) >> + tmp_to_remove_breakpoint = t->to_remove_breakpoint; >> } >> - if (!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) >> + if (!tmp_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); >> + if (current_target.to_stratum == core_stratum) >> + record_core_open_1 (name, from_tty); >> + else >> + record_open_1 (name, from_tty); >> >> /* Reset */ >> record_insn_num = 0; >> record_list = &record_first; >> record_list->next = NULL; >> + >> + /* Set the tmp beneath pointers to beneath pointers. */ >> + record_beneath_to_resume_ops = tmp_to_resume_ops; >> + record_beneath_to_resume = tmp_to_resume; >> + record_beneath_to_wait_ops = tmp_to_wait_ops; >> + record_beneath_to_wait = tmp_to_wait; >> + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; >> + record_beneath_to_store_registers = tmp_to_store_registers; >> + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; >> + record_beneath_to_xfer_partial = tmp_to_xfer_partial; >> + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; >> + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; >> + >> + /* Load if there is argument. */ >> + record_load (name); >> } >> >> static void >> record_close (int quitting) >> { >> + struct record_core_buf_entry *entry; >> + >> if (record_debug) >> fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); >> >> record_list_release (record_list); >> + >> + /* Release record_core_regbuf. */ >> + if (record_core_regbuf) >> + { >> + xfree (record_core_regbuf); >> + record_core_regbuf = NULL; >> + } >> + >> + /* Release record_core_buf_list. */ >> + if (record_core_buf_list) >> + { >> + for (entry = record_core_buf_list->prev; entry; entry = >> entry->prev) >> + { >> + xfree (record_core_buf_list); >> + record_core_buf_list = entry; >> + } >> + record_core_buf_list = NULL; >> + } >> } >> >> static int record_resume_step = 0; >> @@ -584,7 +938,7 @@ record_wait (struct target_ops *ops, >> "record_resume_step = %d\n", >> record_resume_step); >> >> - if (!RECORD_IS_REPLAY) >> + if (!RECORD_IS_REPLAY && ops != &record_core_ops) >> { >> if (record_resume_error) >> { >> @@ -712,76 +1066,9 @@ record_wait (struct target_ops *ops, >> 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 */ >> - /* Nothing to do if the entry is flagged not_accessible. */ >> - if (!record_list->u.mem.mem_entry_not_accessible) >> - { >> - 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 = %s len = %d.\n", >> - host_address_to_string >> (record_list), >> - paddress (gdbarch, >> - >> record_list->u.mem.addr), >> - record_list->u.mem.len); >> + record_exec_entry (regcache, gdbarch, record_list); >> >> - if (target_read_memory (record_list->u.mem.addr, mem, >> - record_list->u.mem.len)) >> - { >> - if (execution_direction != EXEC_REVERSE) >> - error (_("Process record: error reading memory at >> " >> - "addr = %s len = %d."), >> - paddress (gdbarch, >> record_list->u.mem.addr), >> - record_list->u.mem.len); >> - else >> - /* Read failed -- >> - flag entry as not_accessible. */ >> - record_list->u.mem.mem_entry_not_accessible = 1; >> - } >> - else >> - { >> - if (target_write_memory (record_list->u.mem.addr, >> - record_list->u.mem.val, >> - record_list->u.mem.len)) >> - { >> - if (execution_direction != EXEC_REVERSE) >> - error (_("Process record: error writing memory >> at " >> - "addr = %s len = %d."), >> - paddress (gdbarch, >> record_list->u.mem.addr), >> - record_list->u.mem.len); >> - else >> - /* Write failed -- >> - flag entry as not_accessible. */ >> - record_list->u.mem.mem_entry_not_accessible = >> 1; >> - } >> - else >> - { >> - memcpy (record_list->u.mem.val, mem, >> - record_list->u.mem.len); >> - } >> - } >> - } >> - } >> - else >> + if (record_list->type == record_end) >> { >> if (record_debug > 1) >> fprintf_unfiltered (gdb_stdlog, >> @@ -901,6 +1188,7 @@ record_kill (struct target_ops *ops) >> fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); >> >> unpush_target (&record_ops); >> + >> target_kill (); >> } >> >> @@ -945,7 +1233,7 @@ record_registers_change (struct regcache >> record_list = record_arch_list_tail; >> >> if (record_insn_num == record_insn_max_num && record_insn_max_num) >> - record_list_release_first (); >> + record_list_release_first_insn (); >> else >> record_insn_num++; >> } >> @@ -1058,7 +1346,7 @@ record_xfer_partial (struct target_ops * >> record_list = record_arch_list_tail; >> >> if (record_insn_num == record_insn_max_num && record_insn_max_num) >> - record_list_release_first (); >> + record_list_release_first_insn (); >> else >> record_insn_num++; >> } >> @@ -1138,6 +1426,191 @@ init_record_ops (void) >> } >> >> static void >> +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, >> + enum target_signal siggnal) >> +{ >> + record_resume_step = step; >> + record_resume_siggnal = siggnal; >> +} >> + >> +static void >> +record_core_kill (struct target_ops *ops) >> +{ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, "Process record: >> record_core_kill\n"); >> + >> + unpush_target (&record_core_ops); >> +} >> + >> +static void >> +record_core_fetch_registers (struct target_ops *ops, >> + struct regcache *regcache, >> + int regno) >> +{ >> + if (regno < 0) >> + { >> + int num = gdbarch_num_regs (get_regcache_arch (regcache)); >> + int i; >> + >> + for (i = 0; i < num; i ++) >> + regcache_raw_supply (regcache, i, >> + record_core_regbuf + MAX_REGISTER_SIZE * i); >> + } >> + else >> + regcache_raw_supply (regcache, regno, >> + record_core_regbuf + MAX_REGISTER_SIZE * regno); >> +} >> + >> +static void >> +record_core_prepare_to_store (struct regcache *regcache) >> +{ >> +} >> + >> +static void >> +record_core_store_registers (struct target_ops *ops, >> + struct regcache *regcache, >> + int regno) >> +{ >> + if (record_gdb_operation_disable) >> + regcache_raw_collect (regcache, regno, >> + record_core_regbuf + MAX_REGISTER_SIZE * >> regno); >> + else >> + error (_("You can't do that without a process to debug.")); >> +} >> + >> +static LONGEST >> +record_core_xfer_partial (struct target_ops *ops, enum target_object >> object, >> + const char *annex, gdb_byte *readbuf, >> + const gdb_byte *writebuf, ULONGEST offset, >> + LONGEST len) >> +{ >> + if (object == TARGET_OBJECT_MEMORY) >> + { >> + if (record_gdb_operation_disable || !writebuf) >> + { >> + struct target_section *p; >> + for (p = record_core_start; p < record_core_end; p++) >> + { >> + if (offset >= p->addr) >> + { >> + struct record_core_buf_entry *entry; >> + >> + if (offset >= p->endaddr) >> + continue; >> + >> + if (offset + len > p->endaddr) >> + len = p->endaddr - offset; >> + >> + offset -= p->addr; >> + >> + /* Read readbuf or write writebuf p, offset, len. */ >> + /* Check flags. */ >> + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR >> + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) >> == 0) >> + { >> + if (readbuf) >> + memset (readbuf, 0, len); >> + return len; >> + } >> + /* Get record_core_buf_entry. */ >> + for (entry = record_core_buf_list; entry; >> + entry = entry->prev) >> + if (entry->p == p) >> + break; >> + if (writebuf) >> + { >> + if (!entry) >> + { >> + /* Add a new entry. */ >> + entry >> + = (struct record_core_buf_entry *) >> + xmalloc >> + (sizeof (struct >> record_core_buf_entry)); >> + entry->p = p; >> + if (!bfd_malloc_and_get_section (p->bfd, >> + >> p->the_bfd_section, >> + &entry->buf)) >> + { >> + xfree (entry); >> + return 0; >> + } >> + entry->prev = record_core_buf_list; >> + record_core_buf_list = entry; >> + } >> + >> + memcpy (entry->buf + offset, writebuf, (size_t) >> len); >> + } >> + else >> + { >> + if (!entry) >> + return record_beneath_to_xfer_partial >> + (record_beneath_to_xfer_partial_ops, >> + object, annex, readbuf, writebuf, >> + offset, len); >> + >> + memcpy (readbuf, entry->buf + offset, (size_t) >> len); >> + } >> + >> + return len; >> + } >> + } >> + >> + return -1; >> + } >> + else >> + error (_("You can't do that without a process to debug.")); >> + } >> + >> + return record_beneath_to_xfer_partial >> (record_beneath_to_xfer_partial_ops, >> + object, annex, readbuf, >> writebuf, >> + offset, len); >> +} >> + >> +static int >> +record_core_insert_breakpoint (struct gdbarch *gdbarch, >> + struct bp_target_info *bp_tgt) >> +{ >> + return 0; >> +} >> + >> +static int >> +record_core_remove_breakpoint (struct gdbarch *gdbarch, >> + struct bp_target_info *bp_tgt) >> +{ >> + return 0; >> +} >> + >> +int >> +record_core_has_execution (struct target_ops *ops) >> +{ >> + return 1; >> +} >> + >> +static void >> +init_record_core_ops (void) >> +{ >> + record_core_ops.to_shortname = "record_core"; >> + record_core_ops.to_longname = "Process record and replay target"; >> + record_core_ops.to_doc = >> + "Log program while executing and replay execution from log."; >> + record_core_ops.to_open = record_open; >> + record_core_ops.to_close = record_close; >> + record_core_ops.to_resume = record_core_resume; >> + record_core_ops.to_wait = record_wait; >> + record_core_ops.to_kill = record_core_kill; >> + record_core_ops.to_fetch_registers = record_core_fetch_registers; >> + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; >> + record_core_ops.to_store_registers = record_core_store_registers; >> + record_core_ops.to_xfer_partial = record_core_xfer_partial; >> + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; >> + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; >> + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; >> + record_core_ops.to_has_execution = record_core_has_execution; >> + record_core_ops.to_stratum = record_stratum; >> + record_core_ops.to_magic = OPS_MAGIC; >> +} >> + >> +static void >> show_record_debug (struct ui_file *file, int from_tty, >> struct cmd_list_element *c, const char *value) >> { >> @@ -1153,6 +1626,212 @@ cmd_record_start (char *args, int from_t >> execute_command ("target record", from_tty); >> } >> >> +static void >> +cmd_record_load (char *args, int from_tty) >> +{ >> + char buf[512]; >> + >> + snprintf (buf, 512, "target record %s", args); >> + execute_command (buf, from_tty); >> +} >> + >> +static inline void >> +record_write_dump (char *recfilename, int fildes, const void *buf, >> + size_t nbyte) >> +{ >> + if (write (fildes, buf, nbyte) != nbyte) >> + error (_("Failed to write dump of execution records to '%s'."), >> + recfilename); >> +} >> + >> +/* Record log save-file format >> + Version 1 >> + >> + Header: >> + 4 bytes: magic number htonl(0x20090726). >> + NOTE: be sure to change whenever this file format changes! >> + >> + Records: >> + record_end: >> + 1 byte: record type (record_end). >> + record_reg: >> + 1 byte: record type (record_reg). >> + 8 bytes: register id (network byte order). >> + MAX_REGISTER_SIZE bytes: register value. >> + record_mem: >> + 1 byte: record type (record_mem). >> + 8 bytes: memory address (network byte order). >> + 8 bytes: memory length (network byte order). >> + n bytes: memory value (n == memory length). >> +*/ >> + >> +/* Dump the execution log to a file. */ >> + >> +static void >> +cmd_record_dump (char *args, int from_tty) >> +{ >> + char *recfilename, recfilename_buffer[40]; >> + int recfd; >> + struct record_entry *cur_record_list; >> + uint32_t magic; >> + struct regcache *regcache; >> + struct gdbarch *gdbarch; >> + struct cleanup *old_cleanups; >> + struct cleanup *set_cleanups; >> + >> + if (strcmp (current_target.to_shortname, "record") != 0) >> + error (_("Process record is not started.\n")); >> + >> + if (args && *args) >> + recfilename = args; >> + else >> + { >> + /* Default corefile name is "gdb_record.PID". */ >> + snprintf (recfilename_buffer, 40, "gdb_record.%d", >> + PIDGET (inferior_ptid)); >> + recfilename = recfilename_buffer; >> + } >> + >> + /* Open the dump file. */ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + _("Saving recording to file '%s'\n"), >> + recfilename); >> + recfd = open (recfilename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, >> + S_IRUSR | S_IWUSR); >> + if (recfd < 0) >> + error (_("Failed to open '%s' for dump execution records: %s"), >> + recfilename, strerror (errno)); >> + old_cleanups = make_cleanup (record_fd_cleanups, &recfd); >> + >> + /* Save the current record entry to "cur_record_list". */ >> + cur_record_list = record_list; >> + >> + /* Get the values of regcache and gdbarch. */ >> + regcache = get_current_regcache (); >> + gdbarch = get_regcache_arch (regcache); >> + >> + /* Disable the GDB operation record. */ >> + set_cleanups = record_gdb_operation_disable_set (); >> + >> + /* Write the magic code. */ >> + magic = RECORD_FILE_MAGIC; >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, _("\ >> +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), >> + magic); >> + record_write_dump (recfilename, recfd, &magic, 4); >> + >> + /* Reverse execute to the begin of record list. */ >> + while (1) >> + { >> + /* Check for beginning and end of log. */ >> + if (record_list == &record_first) >> + break; >> + >> + record_exec_entry (regcache, gdbarch, record_list); >> + >> + if (record_list->prev) >> + record_list = record_list->prev; >> + } >> + >> + /* Dump the entries to recfd and forward execute to the end of >> + record list. */ >> + while (1) >> + { >> + /* Dump entry. */ >> + if (record_list != &record_first) >> + { >> + uint8_t tmpu8; >> + uint64_t tmpu64; >> + >> + tmpu8 = record_list->type; >> + record_write_dump (recfilename, recfd, &tmpu8, 1); >> + >> + switch (record_list->type) >> + { >> + case record_reg: /* reg */ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, _("\ >> +Writing register %d (1 plus 8 plus %d bytes)\n"), >> + record_list->u.reg.num, >> + MAX_REGISTER_SIZE); >> + >> + tmpu64 = record_list->u.reg.num; >> + if (BYTE_ORDER == LITTLE_ENDIAN) >> + tmpu64 = bswap_64 (tmpu64); >> + record_write_dump (recfilename, recfd, &tmpu64, 8); >> + >> + record_write_dump (recfilename, recfd, >> record_list->u.reg.val, >> + MAX_REGISTER_SIZE); >> + break; >> + >> + case record_mem: /* mem */ >> + if (!record_list->u.mem.mem_entry_not_accessible) >> + { >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, _("\ >> +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), >> + paddress (gdbarch, >> + >> record_list->u.mem.addr), >> + record_list->u.mem.len); >> + >> + tmpu64 = record_list->u.mem.addr; >> + if (BYTE_ORDER == LITTLE_ENDIAN) >> + tmpu64 = bswap_64 (tmpu64); >> + record_write_dump (recfilename, recfd, &tmpu64, 8); >> + >> + tmpu64 = record_list->u.mem.len; >> + if (BYTE_ORDER == LITTLE_ENDIAN) >> + tmpu64 = bswap_64 (tmpu64); >> + record_write_dump (recfilename, recfd, &tmpu64, 8); >> + >> + record_write_dump (recfilename, recfd, >> + record_list->u.mem.val, >> + record_list->u.mem.len); >> + } >> + break; >> + >> + case record_end: >> + /* FIXME: record the contents of record_end rec. */ >> + if (record_debug) >> + fprintf_unfiltered (gdb_stdlog, >> + _("Writing record_end (1 >> byte)\n")); >> + break; >> + } >> + } >> + >> + /* Execute entry. */ >> + record_exec_entry (regcache, gdbarch, record_list); >> + >> + if (record_list->next) >> + record_list = record_list->next; >> + else >> + break; >> + } >> + >> + /* Reverse execute to cur_record_list. */ >> + while (1) >> + { >> + /* Check for beginning and end of log. */ >> + if (record_list == cur_record_list) >> + break; >> + >> + record_exec_entry (regcache, gdbarch, record_list); >> + >> + if (record_list->prev) >> + record_list = record_list->prev; >> + } >> + >> + do_cleanups (set_cleanups); >> + do_cleanups (old_cleanups); >> + >> + /* Succeeded. */ >> + fprintf_filtered (gdb_stdout, _("Saved dump of execution " >> + "records to `%s'.\n"), >> + recfilename); >> +} >> + >> /* Truncate the record log from the present point >> of replay until the end. */ >> >> @@ -1185,7 +1864,12 @@ cmd_record_stop (char *args, int from_tt >> { >> if (!record_list || !from_tty || query (_("Delete recorded log and " >> "stop recording?"))) >> - unpush_target (&record_ops); >> + { >> + if (strcmp (current_target.to_shortname, "record") == 0) >> + unpush_target (&record_ops); >> + else >> + unpush_target (&record_core_ops); >> + } >> } >> else >> printf_unfiltered (_("Process record is not started.\n")); >> @@ -1203,7 +1887,7 @@ set_record_insn_max_num (char *args, int >> "the first ones?\n")); >> >> while (record_insn_num > record_insn_max_num) >> - record_list_release_first (); >> + record_list_release_first_insn (); >> } >> } >> >> @@ -1243,6 +1927,8 @@ info_record_command (char *args, int fro >> void >> _initialize_record (void) >> { >> + struct cmd_list_element *c; >> + >> /* Init record_first. */ >> record_first.prev = NULL; >> record_first.next = NULL; >> @@ -1250,6 +1936,8 @@ _initialize_record (void) >> >> init_record_ops (); >> add_target (&record_ops); >> + init_record_core_ops (); >> + add_target (&record_core_ops); >> >> add_setshow_zinteger_cmd ("record", no_class, &record_debug, >> _("Set debugging of record/replay feature."), >> @@ -1259,9 +1947,10 @@ _initialize_record (void) >> NULL, show_record_debug, &setdebuglist, >> &showdebuglist); >> >> - add_prefix_cmd ("record", class_obscure, cmd_record_start, >> - _("Abbreviated form of \"target record\" command."), >> - &record_cmdlist, "record ", 0, &cmdlist); >> + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, >> + _("Abbreviated form of \"target record\" command."), >> + &record_cmdlist, "record ", 0, &cmdlist); >> + set_cmd_completer (c, filename_completer); >> add_com_alias ("rec", "record", class_obscure, 1); >> add_prefix_cmd ("record", class_support, set_record_command, >> _("Set record options"), &set_record_cmdlist, >> @@ -1276,6 +1965,16 @@ _initialize_record (void) >> "info record ", 0, &infolist); >> add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); >> >> + c = add_cmd ("load", class_obscure, cmd_record_load, >> + _("Load previously dumped execution records from \ >> +a file given as argument."), >> + &record_cmdlist); >> + set_cmd_completer (c, filename_completer); >> + c = add_cmd ("dump", class_obscure, cmd_record_dump, >> + _("Dump the execution records to a file.\n\ >> +Argument is optional filename. Default filename is >> 'gdb_record.<process_id>'."), >> + &record_cmdlist); >> + set_cmd_completer (c, filename_completer); >> >> add_cmd ("delete", class_obscure, cmd_record_delete, >> _("Delete the rest of execution log and start recording it >> anew."), > > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-25 8:47 ` Hui Zhu @ 2009-08-26 1:40 ` Michael Snyder 2009-08-26 2:59 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-26 1:40 UTC (permalink / raw) To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches [-- Attachment #1: Type: text/plain, Size: 1259 bytes --] Hui Zhu wrote: > On Tue, Aug 25, 2009 at 01:51, Michael Snyder<msnyder@vmware.com> wrote: >> Now I can comment on the dump/load part of the patch. >> It does not seem as if previous issues have been addressed: >> 1) Core file and record file are separate, no way to verify >> that they correspond to each other. > >> 2) Previous log not cleared when loading a new log. > For it, I think we don't need worry about it. Because I make the > record load together with record_open. So each time when we load > record, the record list is empty. I would like to see it made explicit, please. >> Have you considered combining this patch with the idea that I >> proposed -- storing the record log in the core file? Seems like >> that would be very easy now -- gdb already has the core file open, >> and there is a global pointer to the BFD. >> See gdbcore.h:extern bfd* core_bfd; > > Cool. If we just want record_core_ops support load, we can remove the > cmd "record load". :) > >> In fact, just for fun, I've merged the two patches in my >> local tree. It seems to work quite well. I can just post >> it here if you like, or here's a hint to show you how it goes: > > > Why not? Please post it. I can not wait to try it. :) Great -- attached. [-- Attachment #2: record.c --] [-- Type: text/x-csrc, Size: 60387 bytes --] /* 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 "completer.h" #include "arch-utils.h" #include "gdbcore.h" #include "exec.h" #include "record.h" #include <byteswap.h> #include <signal.h> #include <netinet/in.h> #include "elf-bfd.h" /* For dump/load commands. */ #include "gcore.h" #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) #define RECORD_FILE_MAGIC htonl(0x20090726) /* These are the core struct of record function. An record_entry is a record of the value change of a register ("record_reg") or a part of memory ("record_mem"). And each instruction must has a struct record_entry ("record_end") that points out this is the last struct record_entry of this instruction. Each struct record_entry is linked to "record_list" by "prev" and "next". */ struct record_reg_entry { int num; gdb_byte *val; }; struct record_mem_entry { CORE_ADDR addr; int len; /* Set this flag if target memory for this entry can no longer be accessed. */ int mem_entry_not_accessible; gdb_byte *val; }; enum record_type { record_end = 0, record_reg, record_mem }; struct record_entry { struct record_entry *prev; struct record_entry *next; enum record_type type; union { /* reg */ struct record_reg_entry reg; /* mem */ struct record_mem_entry mem; } u; }; struct record_core_buf_entry { struct record_core_buf_entry *prev; struct target_section *p; bfd_byte *buf; }; /* This is the debug switch for process record. */ int record_debug = 0; /* Record buf with core target. */ static gdb_byte *record_core_regbuf = NULL; static struct target_section *record_core_start; static struct target_section *record_core_end; static struct record_core_buf_entry *record_core_buf_list = NULL; /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; static struct record_entry *record_arch_list_head = NULL; static struct record_entry *record_arch_list_tail = NULL; /* 1 ask user. 0 auto delete the last struct record_entry. */ static int record_stop_at_limit = 1; static int record_insn_max_num = DEFAULT_RECORD_INSN_MAX_NUM; static int record_insn_num = 0; /* The target_ops of process record. */ static struct target_ops record_ops; static struct target_ops record_core_ops; /* The beneath function pointers. */ static struct target_ops *record_beneath_to_resume_ops; static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int, enum target_signal); static struct target_ops *record_beneath_to_wait_ops; static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t, struct target_waitstatus *, int); static struct target_ops *record_beneath_to_store_registers_ops; static void (*record_beneath_to_store_registers) (struct target_ops *, struct regcache *, int regno); static struct target_ops *record_beneath_to_xfer_partial_ops; static LONGEST (*record_beneath_to_xfer_partial) (struct target_ops *ops, enum target_object object, const char *annex, gdb_byte *readbuf, const gdb_byte *writebuf, ULONGEST offset, LONGEST len); static int (*record_beneath_to_insert_breakpoint) (struct gdbarch *, struct bp_target_info *); static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); static void record_list_release (struct record_entry *rec) { struct record_entry *tmp; if (!rec) return; while (rec->next) { rec = rec->next; } while (rec->prev) { tmp = rec; rec = rec->prev; if (tmp->type == record_reg) xfree (tmp->u.reg.val); else if (tmp->type == record_mem) xfree (tmp->u.mem.val); xfree (tmp); } if (rec != &record_first) xfree (rec); record_insn_num = 0; record_list = &record_first; record_list->next = NULL; record_arch_list_tail = NULL; record_arch_list_tail = NULL; } static void record_list_release_next (void) { struct record_entry *rec = record_list; struct record_entry *tmp = rec->next; rec->next = NULL; while (tmp) { rec = tmp->next; if (tmp->type == record_reg) record_insn_num--; else if (tmp->type == record_reg) xfree (tmp->u.reg.val); else if (tmp->type == record_mem) xfree (tmp->u.mem.val); xfree (tmp); tmp = rec; } } static void record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; if (!record_first.next) return; while (1) { type = record_first.next->type; if (type == record_reg) xfree (record_first.next->u.reg.val); else if (type == record_mem) xfree (record_first.next->u.mem.val); tmp = record_first.next; record_first.next = tmp->next; xfree (tmp); if (!record_first.next) { gdb_assert (record_insn_num == 1); break; } record_first.next->prev = &record_first; if (type == record_end) break; } record_insn_num--; } /* Add a struct record_entry to record_arch_list. */ static void record_arch_list_add (struct record_entry *rec) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: record_arch_list_add %s.\n", host_address_to_string (rec)); if (record_arch_list_tail) { record_arch_list_tail->next = rec; rec->prev = record_arch_list_tail; record_arch_list_tail = rec; } else { record_arch_list_head = rec; record_arch_list_tail = rec; } } /* Record the value of a register NUM to record_arch_list. */ int record_arch_list_add_reg (struct regcache *regcache, int num) { struct record_entry *rec; if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: add register num = %d to " "record list.\n", num); rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); rec->prev = NULL; rec->next = NULL; rec->type = record_reg; rec->u.reg.num = num; regcache_raw_read (regcache, num, rec->u.reg.val); record_arch_list_add (rec); return 0; } /* Record the value of a region of memory whose address is ADDR and length is LEN to record_arch_list. */ int record_arch_list_add_mem (CORE_ADDR addr, int len) { struct record_entry *rec; if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: add mem addr = %s len = %d to " "record list.\n", paddress (target_gdbarch, addr), len); if (!addr) return 0; rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->u.mem.val = (gdb_byte *) xmalloc (len); rec->prev = NULL; rec->next = NULL; rec->type = record_mem; rec->u.mem.addr = addr; rec->u.mem.len = len; rec->u.mem.mem_entry_not_accessible = 0; if (target_read_memory (addr, rec->u.mem.val, len)) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: error reading memory at " "addr = %s len = %d.\n", paddress (target_gdbarch, addr), len); xfree (rec->u.mem.val); xfree (rec); return -1; } record_arch_list_add (rec); return 0; } /* Add a record_end type struct record_entry to record_arch_list. */ int record_arch_list_add_end (void) { struct record_entry *rec; if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: add end to arch list.\n"); rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->prev = NULL; rec->next = NULL; rec->type = record_end; record_arch_list_add (rec); return 0; } static void record_check_insn_num (int set_terminal) { if (record_insn_max_num) { gdb_assert (record_insn_num <= record_insn_max_num); if (record_insn_num == record_insn_max_num) { /* Ask user what to do. */ if (record_stop_at_limit) { int q; if (set_terminal) target_terminal_ours (); q = yquery (_("Do you want to auto delete previous execution " "log entries when record/replay buffer becomes " "full (record stop-at-limit)?")); if (set_terminal) target_terminal_inferior (); if (q) record_stop_at_limit = 0; else error (_("Process record: stoped by user.")); } } } } static void record_arch_list_cleanups (void *ignore) { record_list_release (record_arch_list_tail); } /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ static int record_message (void *args) { int ret; struct regcache *regcache = args; struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; /* Check record_insn_num. */ record_check_insn_num (1); ret = gdbarch_process_record (get_regcache_arch (regcache), regcache, regcache_read_pc (regcache)); if (ret > 0) error (_("Process record: inferior program stopped.")); if (ret < 0) error (_("Process record: failed to record execution log.")); discard_cleanups (old_cleanups); record_list->next = record_arch_list_head; record_arch_list_head->prev = record_list; record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) record_list_release_first_insn (); else record_insn_num++; return 1; } static int do_record_message (struct regcache *regcache) { return catch_errors (record_message, regcache, NULL, RETURN_MASK_ALL); } /* Set to 1 if record_store_registers and record_xfer_partial doesn't need record. */ static int record_gdb_operation_disable = 0; struct cleanup * record_gdb_operation_disable_set (void) { struct cleanup *old_cleanups = NULL; old_cleanups = make_cleanup_restore_integer (&record_gdb_operation_disable); record_gdb_operation_disable = 1; return old_cleanups; } static inline void record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, struct record_entry *entry) { switch (entry->type) { case record_reg: /* reg */ { gdb_byte reg[MAX_REGISTER_SIZE]; if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: record_reg %s to " "inferior num = %d.\n", host_address_to_string (entry), entry->u.reg.num); regcache_cooked_read (regcache, entry->u.reg.num, reg); regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); } break; case record_mem: /* mem */ { if (!record_list->u.mem.mem_entry_not_accessible) { gdb_byte *mem = alloca (entry->u.mem.len); if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: record_mem %s to " "inferior addr = %s len = %d.\n", host_address_to_string (entry), paddress (gdbarch, entry->u.mem.addr), record_list->u.mem.len); if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) warning (_("Process record: error reading memory at " "addr = %s len = %d."), paddress (gdbarch, entry->u.mem.addr), entry->u.mem.len); } else { if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, entry->u.mem.len)) { record_list->u.mem.mem_entry_not_accessible = 1; if (record_debug) warning (_("Process record: error writing memory at " "addr = %s len = %d."), paddress (gdbarch, entry->u.mem.addr), entry->u.mem.len); } } memcpy (entry->u.mem.val, mem, entry->u.mem.len); } } break; } } /* bfdcore_read -- read bytes from a core file section. */ static int bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset) { int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len); if (ret) *offset += len; return ret; } /* Load the execution log from a file. */ static void record_load (void) { int recfd; uint32_t magic; struct cleanup *old_cleanups2; struct record_entry *rec; int insn_number = 0; asection *osec; void nullify_last_target_wait_ptid (void); /* We load the execution log from the open core bfd, if there is one. */ if (core_bfd == NULL) return; /* Open the load file. */ if (record_debug) fprintf_filtered (gdb_stdlog, _("Restoring recording from core file.\n")); /* Now need to find our special note section. */ osec = bfd_get_section_by_name (core_bfd, "null0"); printf_filtered ("Find precord section %s.\n", osec ? "succeeded" : "failed"); if (osec) { int i, len; int bfd_offset = 0; if (record_debug) fprintf_filtered (gdb_stdlog, "osec name = '%s'\n", bfd_section_name (core_bfd, osec)); len = (int) bfd_section_size (core_bfd, osec); printf_filtered ("osec size = %d\n", len); /* Check the magic code. */ if (!bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset)) error (_("Failed to read 'magic' from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (magic != RECORD_FILE_MAGIC) error (_("version mis-match / file format error.")); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), magic); /* Free any existing record log, and load the entries in core_bfd to the new record log. */ record_list_release (record_arch_list_tail); old_cleanups2 = make_cleanup (record_arch_list_cleanups, 0); while (1) { uint8_t tmpu8; uint64_t tmpu64; /* FIXME: Check offset for end-of-section. */ if (!bfdcore_read (core_bfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset)) break; switch (tmpu8) { case record_reg: /* reg */ /* FIXME: abstract out into an 'insert' function. */ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->u.reg.val = (gdb_byte *) xcalloc (1, MAX_REGISTER_SIZE); rec->prev = NULL; rec->next = NULL; rec->type = record_reg; /* Get num. */ /* FIXME: register num does not need 8 bytes. */ if (!bfdcore_read (core_bfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to read regnum from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); rec->u.reg.num = tmpu64; /* Get val. */ if (!bfdcore_read (core_bfd, osec, rec->u.reg.val, MAX_REGISTER_SIZE, &bfd_offset)) error (_("Failed to read regval from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Reading register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"), rec->u.reg.num, *(ULONGEST *) rec->u.reg.val, MAX_REGISTER_SIZE); record_arch_list_add (rec); break; case record_mem: /* mem */ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->prev = NULL; rec->next = NULL; rec->type = record_mem; /* Get addr. */ if (!bfdcore_read (core_bfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to read memaddr from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); rec->u.mem.addr = tmpu64; /* Get len. */ /* FIXME: len does not need 8 bytes. */ if (!bfdcore_read (core_bfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to read memlen from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); rec->u.mem.len = tmpu64; rec->u.mem.mem_entry_not_accessible = 0; rec->u.mem.val = (gdb_byte *) xmalloc (rec->u.mem.len); /* Get val. */ if (!bfdcore_read (core_bfd, osec, rec->u.mem.val, rec->u.mem.len, &bfd_offset)) error (_("Failed to read memval from core file (%s)"), bfd_errmsg (bfd_get_error ())); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Reading memory 0x%08x (1 plus 8 plus %d bytes)\n"), (unsigned int) rec->u.mem.addr, rec->u.mem.len); record_arch_list_add (rec); break; case record_end: /* end */ /* FIXME: restore the contents of record_end rec. */ rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); rec->prev = NULL; rec->next = NULL; rec->type = record_end; if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Reading record_end (one byte)\n")); record_arch_list_add (rec); insn_number ++; break; default: error (_("Format of core file is not right.")); break; } } } discard_cleanups (old_cleanups2); /* Add record_arch_list_head to the end of record list. (??? FIXME)*/ for (rec = record_list; rec->next; rec = rec->next) ; rec->next = record_arch_list_head; record_arch_list_head->prev = rec; /* Update record_insn_num and record_insn_max_num. */ record_insn_num = insn_number; if (record_insn_num > record_insn_max_num) { record_insn_max_num = record_insn_num; warning (_("Auto increase record/replay buffer limit to %d."), record_insn_max_num); } /* Succeeded. */ fprintf_filtered (gdb_stdout, "Loaded records from core file.\n"); registers_changed (); reinit_frame_cache (); nullify_last_target_wait_ptid (); print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); } static struct target_ops *tmp_to_resume_ops; static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, enum target_signal); static struct target_ops *tmp_to_wait_ops; static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, struct target_waitstatus *, int); static struct target_ops *tmp_to_store_registers_ops; static void (*tmp_to_store_registers) (struct target_ops *, struct regcache *, int regno); static struct target_ops *tmp_to_xfer_partial_ops; static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, enum target_object object, const char *annex, gdb_byte *readbuf, const gdb_byte *writebuf, ULONGEST offset, LONGEST len); static int (*tmp_to_insert_breakpoint) (struct gdbarch *, struct bp_target_info *); static int (*tmp_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); static void record_core_open_1 (char *name, int from_tty) { struct regcache *regcache = get_current_regcache (); int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); int i; if (!name || (name && !*name)) error (_("Argument for gdb record filename required.\n")); /* Get record_core_regbuf. */ target_fetch_registers (regcache, -1); record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); for (i = 0; i < regnum; i ++) regcache_raw_collect (regcache, i, record_core_regbuf + MAX_REGISTER_SIZE * i); /* Get record_core_start and record_core_end. */ if (build_section_table (core_bfd, &record_core_start, &record_core_end)) { xfree (record_core_regbuf); record_core_regbuf = NULL; error (_("\"%s\": Can't find sections: %s"), bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); } push_target (&record_core_ops); } static void record_open_1 (char *name, int from_tty) { struct target_ops *t; /* check exec */ if (!target_has_execution) error (_("Process record: the program is not being run.")); if (non_stop) error (_("Process record target can't debug inferior in non-stop mode " "(non-stop).")); if (target_async_permitted) error (_("Process record target can't debug inferior in asynchronous " "mode (target-async).")); if (!gdbarch_process_record_p (target_gdbarch)) error (_("Process record: the current architecture doesn't support " "record function.")); if (!tmp_to_resume) error (_("Process record can't get to_resume.")); if (!tmp_to_wait) error (_("Process record can't get to_wait.")); if (!tmp_to_store_registers) error (_("Process record can't get to_store_registers.")); if (!tmp_to_insert_breakpoint) error (_("Process record can't get to_insert_breakpoint.")); if (!tmp_to_remove_breakpoint) error (_("Process record can't get to_remove_breakpoint.")); push_target (&record_ops); } static void record_open (char *name, int from_tty) { struct target_ops *t; if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); /* Check if record target is already running. */ if (current_target.to_stratum == record_stratum) { if (!nquery (_("Process record target already running, do you want to delete " "the old record log?"))) return; } /* Reset the tmp beneath pointers. */ tmp_to_resume_ops = NULL; tmp_to_resume = NULL; tmp_to_wait_ops = NULL; tmp_to_wait = NULL; tmp_to_store_registers_ops = NULL; tmp_to_store_registers = NULL; tmp_to_xfer_partial_ops = NULL; tmp_to_xfer_partial = NULL; tmp_to_insert_breakpoint = NULL; tmp_to_remove_breakpoint = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) { if (!tmp_to_resume) { tmp_to_resume = t->to_resume; tmp_to_resume_ops = t; } if (!tmp_to_wait) { tmp_to_wait = t->to_wait; tmp_to_wait_ops = t; } if (!tmp_to_store_registers) { tmp_to_store_registers = t->to_store_registers; tmp_to_store_registers_ops = t; } if (!tmp_to_xfer_partial) { tmp_to_xfer_partial = t->to_xfer_partial; tmp_to_xfer_partial_ops = t; } if (!tmp_to_insert_breakpoint) tmp_to_insert_breakpoint = t->to_insert_breakpoint; if (!tmp_to_remove_breakpoint) tmp_to_remove_breakpoint = t->to_remove_breakpoint; } if (!tmp_to_xfer_partial) error (_("Process record can't get to_xfer_partial.")); if (current_target.to_stratum == core_stratum) record_core_open_1 (name, from_tty); else record_open_1 (name, from_tty); /* Reset */ record_insn_num = 0; record_list = &record_first; record_list->next = NULL; /* Set the tmp beneath pointers to beneath pointers. */ record_beneath_to_resume_ops = tmp_to_resume_ops; record_beneath_to_resume = tmp_to_resume; record_beneath_to_wait_ops = tmp_to_wait_ops; record_beneath_to_wait = tmp_to_wait; record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; record_beneath_to_store_registers = tmp_to_store_registers; record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; record_beneath_to_xfer_partial = tmp_to_xfer_partial; record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; /* Load record log if corefile contains one. */ record_load (); } static void record_close (int quitting) { struct record_core_buf_entry *entry; if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); /* Release record_core_regbuf. */ if (record_core_regbuf) { xfree (record_core_regbuf); record_core_regbuf = NULL; } /* Release record_core_buf_list. */ if (record_core_buf_list) { for (entry = record_core_buf_list->prev; entry; entry = entry->prev) { xfree (record_core_buf_list); record_core_buf_list = entry; } record_core_buf_list = NULL; } } static int record_resume_step = 0; static enum target_signal record_resume_siggnal; static int record_resume_error; static void record_resume (struct target_ops *ops, ptid_t ptid, int step, enum target_signal siggnal) { record_resume_step = step; record_resume_siggnal = siggnal; if (!RECORD_IS_REPLAY) { if (do_record_message (get_current_regcache ())) { record_resume_error = 0; } else { record_resume_error = 1; return; } record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, siggnal); } } static int record_get_sig = 0; static void record_sig_handler (int signo) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: get a signal\n"); /* It will break the running inferior in replay mode. */ record_resume_step = 1; /* It will let record_wait set inferior status to get the signal SIGINT. */ record_get_sig = 1; } static void record_wait_cleanups (void *ignore) { if (execution_direction == EXEC_REVERSE) { if (record_list->next) record_list = record_list->next; } else record_list = record_list->prev; } /* In replay mode, this function examines the recorded log and determines where to stop. */ static ptid_t record_wait (struct target_ops *ops, ptid_t ptid, struct target_waitstatus *status, int options) { struct cleanup *set_cleanups = record_gdb_operation_disable_set (); if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_wait " "record_resume_step = %d\n", record_resume_step); if (!RECORD_IS_REPLAY && ops != &record_core_ops) { if (record_resume_error) { /* If record_resume get error, return directly. */ status->kind = TARGET_WAITKIND_STOPPED; status->value.sig = TARGET_SIGNAL_ABRT; return inferior_ptid; } if (record_resume_step) { /* This is a single step. */ return record_beneath_to_wait (record_beneath_to_wait_ops, ptid, status, 0); } else { /* This is not a single step. */ ptid_t ret; CORE_ADDR tmp_pc; while (1) { ret = record_beneath_to_wait (record_beneath_to_wait_ops, ptid, status, 0); if (status->kind == TARGET_WAITKIND_STOPPED && status->value.sig == TARGET_SIGNAL_TRAP) { /* Check if there is a breakpoint. */ registers_changed (); tmp_pc = regcache_read_pc (get_current_regcache ()); if (breakpoint_inserted_here_p (tmp_pc)) { /* There is a breakpoint. */ CORE_ADDR decr_pc_after_break = gdbarch_decr_pc_after_break (get_regcache_arch (get_current_regcache ())); if (decr_pc_after_break) { regcache_write_pc (get_thread_regcache (ret), tmp_pc + decr_pc_after_break); } } else { /* There is not a breakpoint. */ if (!do_record_message (get_current_regcache ())) { break; } record_beneath_to_resume (record_beneath_to_resume_ops, ptid, 1, record_resume_siggnal); continue; } } /* The inferior is broken by a breakpoint or a signal. */ break; } return ret; } } else { struct regcache *regcache = get_current_regcache (); struct gdbarch *gdbarch = get_regcache_arch (regcache); int continue_flag = 1; int first_record_end = 1; struct cleanup *old_cleanups = make_cleanup (record_wait_cleanups, 0); CORE_ADDR tmp_pc; status->kind = TARGET_WAITKIND_STOPPED; /* Check breakpoint when forward execute. */ if (execution_direction == EXEC_FORWARD) { tmp_pc = regcache_read_pc (regcache); if (breakpoint_inserted_here_p (tmp_pc)) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: break at %s.\n", paddress (gdbarch, tmp_pc)); if (gdbarch_decr_pc_after_break (gdbarch) && !record_resume_step) regcache_write_pc (regcache, tmp_pc + gdbarch_decr_pc_after_break (gdbarch)); goto replay_out; } } record_get_sig = 0; signal (SIGINT, record_sig_handler); /* If GDB is in terminal_inferior mode, it will not get the signal. And in GDB replay mode, GDB doesn't need to be in terminal_inferior mode, because inferior will not executed. Then set it to terminal_ours to make GDB get the signal. */ target_terminal_ours (); /* In EXEC_FORWARD mode, record_list points to the tail of prev instruction. */ if (execution_direction == EXEC_FORWARD && record_list->next) record_list = record_list->next; /* Loop over the record_list, looking for the next place to stop. */ do { /* Check for beginning and end of log. */ if (execution_direction == EXEC_REVERSE && record_list == &record_first) { /* Hit beginning of record log in reverse. */ status->kind = TARGET_WAITKIND_NO_HISTORY; break; } if (execution_direction != EXEC_REVERSE && !record_list->next) { /* Hit end of record log going forward. */ status->kind = TARGET_WAITKIND_NO_HISTORY; break; } record_exec_entry (regcache, gdbarch, record_list); if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: record_end %s to " "inferior.\n", host_address_to_string (record_list)); if (first_record_end && execution_direction == EXEC_REVERSE) { /* When reverse excute, the first record_end is the part of current instruction. */ first_record_end = 0; } else { /* In EXEC_REVERSE mode, this is the record_end of prev instruction. In EXEC_FORWARD mode, this is the record_end of current instruction. */ /* step */ if (record_resume_step) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, "Process record: step.\n"); continue_flag = 0; } /* check breakpoint */ tmp_pc = regcache_read_pc (regcache); if (breakpoint_inserted_here_p (tmp_pc)) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: break " "at %s.\n", paddress (gdbarch, tmp_pc)); if (gdbarch_decr_pc_after_break (gdbarch) && execution_direction == EXEC_FORWARD && !record_resume_step) regcache_write_pc (regcache, tmp_pc + gdbarch_decr_pc_after_break (gdbarch)); continue_flag = 0; } } } if (continue_flag) { if (execution_direction == EXEC_REVERSE) { if (record_list->prev) record_list = record_list->prev; } else { if (record_list->next) record_list = record_list->next; } } } while (continue_flag); signal (SIGINT, handle_sigint); replay_out: if (record_get_sig) status->value.sig = TARGET_SIGNAL_INT; else status->value.sig = TARGET_SIGNAL_TRAP; discard_cleanups (old_cleanups); } do_cleanups (set_cleanups); return inferior_ptid; } static void record_disconnect (struct target_ops *target, char *args, int from_tty) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_disconnect\n"); unpush_target (&record_ops); target_disconnect (args, from_tty); } static void record_detach (struct target_ops *ops, char *args, int from_tty) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_detach\n"); unpush_target (&record_ops); target_detach (args, from_tty); } static void record_mourn_inferior (struct target_ops *ops) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: " "record_mourn_inferior\n"); unpush_target (&record_ops); target_mourn_inferior (); } /* Close process record target before killing the inferior process. */ static void record_kill (struct target_ops *ops) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); target_kill (); } /* Record registers change (by user or by GDB) to list as an instruction. */ static void record_registers_change (struct regcache *regcache, int regnum) { /* Check record_insn_num. */ record_check_insn_num (0); record_arch_list_head = NULL; record_arch_list_tail = NULL; if (regnum < 0) { int i; for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) { if (record_arch_list_add_reg (regcache, i)) { record_list_release (record_arch_list_tail); error (_("Process record: failed to record execution log.")); } } } else { if (record_arch_list_add_reg (regcache, regnum)) { record_list_release (record_arch_list_tail); error (_("Process record: failed to record execution log.")); } } if (record_arch_list_add_end ()) { record_list_release (record_arch_list_tail); error (_("Process record: failed to record execution log.")); } record_list->next = record_arch_list_head; record_arch_list_head->prev = record_list; record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) record_list_release_first_insn (); else record_insn_num++; } static void record_store_registers (struct target_ops *ops, struct regcache *regcache, int regno) { if (!record_gdb_operation_disable) { if (RECORD_IS_REPLAY) { int n; /* Let user choose if he wants to write register or not. */ if (regno < 0) n = nquery (_("Because GDB is in replay mode, changing the " "value of a register will make the execution " "log unusable from this point onward. " "Change all registers?")); else n = nquery (_("Because GDB is in replay mode, changing the value " "of a register will make the execution log unusable " "from this point onward. Change register %s?"), gdbarch_register_name (get_regcache_arch (regcache), regno)); if (!n) { /* Invalidate the value of regcache that was set in function "regcache_raw_write". */ if (regno < 0) { int i; for (i = 0; i < gdbarch_num_regs (get_regcache_arch (regcache)); i++) regcache_invalidate (regcache, i); } else regcache_invalidate (regcache, regno); error (_("Process record canceled the operation.")); } /* Destroy the record from here forward. */ record_list_release_next (); } record_registers_change (regcache, regno); } record_beneath_to_store_registers (record_beneath_to_store_registers_ops, regcache, regno); } /* Behavior is conditional on RECORD_IS_REPLAY. In replay mode, we cannot write memory unles we are willing to invalidate the record/replay log from this point forward. */ static LONGEST record_xfer_partial (struct target_ops *ops, enum target_object object, const char *annex, gdb_byte *readbuf, const gdb_byte *writebuf, ULONGEST offset, LONGEST len) { if (!record_gdb_operation_disable && (object == TARGET_OBJECT_MEMORY || object == TARGET_OBJECT_RAW_MEMORY) && writebuf) { if (RECORD_IS_REPLAY) { /* Let user choose if he wants to write memory or not. */ if (!nquery (_("Because GDB is in replay mode, writing to memory " "will make the execution log unusable from this " "point onward. Write memory at address %s?"), paddress (target_gdbarch, offset))) error (_("Process record canceled the operation.")); /* Destroy the record from here forward. */ record_list_release_next (); } /* Check record_insn_num */ record_check_insn_num (0); /* Record registers change to list as an instruction. */ record_arch_list_head = NULL; record_arch_list_tail = NULL; if (record_arch_list_add_mem (offset, len)) { record_list_release (record_arch_list_tail); if (record_debug) fprintf_unfiltered (gdb_stdlog, _("Process record: failed to record " "execution log.")); return -1; } if (record_arch_list_add_end ()) { record_list_release (record_arch_list_tail); if (record_debug) fprintf_unfiltered (gdb_stdlog, _("Process record: failed to record " "execution log.")); return -1; } record_list->next = record_arch_list_head; record_arch_list_head->prev = record_list; record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) record_list_release_first_insn (); else record_insn_num++; } return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); } /* Behavior is conditional on RECORD_IS_REPLAY. We will not actually insert or remove breakpoints when replaying, nor when recording. */ static int record_insert_breakpoint (struct gdbarch *gdbarch, struct bp_target_info *bp_tgt) { if (!RECORD_IS_REPLAY) { struct cleanup *old_cleanups = record_gdb_operation_disable_set (); int ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt); do_cleanups (old_cleanups); return ret; } return 0; } static int record_remove_breakpoint (struct gdbarch *gdbarch, struct bp_target_info *bp_tgt) { if (!RECORD_IS_REPLAY) { struct cleanup *old_cleanups = record_gdb_operation_disable_set (); int ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt); do_cleanups (old_cleanups); return ret; } return 0; } static int record_can_execute_reverse (void) { return 1; } static void init_record_ops (void) { record_ops.to_shortname = "record"; record_ops.to_longname = "Process record and replay target"; record_ops.to_doc = "Log program while executing and replay execution from log."; record_ops.to_open = record_open; record_ops.to_close = record_close; record_ops.to_resume = record_resume; record_ops.to_wait = record_wait; record_ops.to_disconnect = record_disconnect; record_ops.to_detach = record_detach; record_ops.to_mourn_inferior = record_mourn_inferior; record_ops.to_kill = record_kill; record_ops.to_create_inferior = find_default_create_inferior; record_ops.to_store_registers = record_store_registers; record_ops.to_xfer_partial = record_xfer_partial; record_ops.to_insert_breakpoint = record_insert_breakpoint; record_ops.to_remove_breakpoint = record_remove_breakpoint; record_ops.to_can_execute_reverse = record_can_execute_reverse; record_ops.to_stratum = record_stratum; record_ops.to_magic = OPS_MAGIC; } static void record_core_resume (struct target_ops *ops, ptid_t ptid, int step, enum target_signal siggnal) { record_resume_step = step; record_resume_siggnal = siggnal; } static void record_core_kill (struct target_ops *ops) { if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); unpush_target (&record_core_ops); } static void record_core_fetch_registers (struct target_ops *ops, struct regcache *regcache, int regno) { if (regno < 0) { int num = gdbarch_num_regs (get_regcache_arch (regcache)); int i; for (i = 0; i < num; i ++) regcache_raw_supply (regcache, i, record_core_regbuf + MAX_REGISTER_SIZE * i); } else regcache_raw_supply (regcache, regno, record_core_regbuf + MAX_REGISTER_SIZE * regno); } static void record_core_prepare_to_store (struct regcache *regcache) { } static void record_core_store_registers (struct target_ops *ops, struct regcache *regcache, int regno) { if (record_gdb_operation_disable) regcache_raw_collect (regcache, regno, record_core_regbuf + MAX_REGISTER_SIZE * regno); else error (_("You can't do that without a process to debug.")); } static LONGEST record_core_xfer_partial (struct target_ops *ops, enum target_object object, const char *annex, gdb_byte *readbuf, const gdb_byte *writebuf, ULONGEST offset, LONGEST len) { if (object == TARGET_OBJECT_MEMORY) { if (record_gdb_operation_disable || !writebuf) { struct target_section *p; for (p = record_core_start; p < record_core_end; p++) { if (offset >= p->addr) { struct record_core_buf_entry *entry; if (offset >= p->endaddr) continue; if (offset + len > p->endaddr) len = p->endaddr - offset; offset -= p->addr; /* Read readbuf or write writebuf p, offset, len. */ /* Check flags. */ if (p->the_bfd_section->flags & SEC_CONSTRUCTOR || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) { if (readbuf) memset (readbuf, 0, len); return len; } /* Get record_core_buf_entry. */ for (entry = record_core_buf_list; entry; entry = entry->prev) if (entry->p == p) break; if (writebuf) { if (!entry) { /* Add a new entry. */ entry = (struct record_core_buf_entry *) xmalloc (sizeof (struct record_core_buf_entry)); entry->p = p; if (!bfd_malloc_and_get_section (p->bfd, p->the_bfd_section, &entry->buf)) { xfree (entry); return 0; } entry->prev = record_core_buf_list; record_core_buf_list = entry; } memcpy (entry->buf + offset, writebuf, (size_t) len); } else { if (!entry) return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); memcpy (readbuf, entry->buf + offset, (size_t) len); } return len; } } return -1; } else error (_("You can't do that without a process to debug.")); } return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, object, annex, readbuf, writebuf, offset, len); } static int record_core_insert_breakpoint (struct gdbarch *gdbarch, struct bp_target_info *bp_tgt) { return 0; } static int record_core_remove_breakpoint (struct gdbarch *gdbarch, struct bp_target_info *bp_tgt) { return 0; } int record_core_has_execution (struct target_ops *ops) { return 1; } static void init_record_core_ops (void) { record_core_ops.to_shortname = "record_core"; record_core_ops.to_longname = "Process record and replay target"; record_core_ops.to_doc = "Log program while executing and replay execution from log."; record_core_ops.to_open = record_open; record_core_ops.to_close = record_close; record_core_ops.to_resume = record_core_resume; record_core_ops.to_wait = record_wait; record_core_ops.to_kill = record_core_kill; record_core_ops.to_fetch_registers = record_core_fetch_registers; record_core_ops.to_prepare_to_store = record_core_prepare_to_store; record_core_ops.to_store_registers = record_core_store_registers; record_core_ops.to_xfer_partial = record_core_xfer_partial; record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; record_core_ops.to_can_execute_reverse = record_can_execute_reverse; record_core_ops.to_has_execution = record_core_has_execution; record_core_ops.to_stratum = record_stratum; record_core_ops.to_magic = OPS_MAGIC; } static void show_record_debug (struct ui_file *file, int from_tty, struct cmd_list_element *c, const char *value) { fprintf_filtered (file, _("Debugging of process record target is %s.\n"), value); } /* Alias for "target record". */ static void cmd_record_start (char *args, int from_tty) { execute_command ("target record", from_tty); } static void cmd_record_load (char *args, int from_tty) { char buf[512]; snprintf (buf, 512, "target record %s", args); execute_command (buf, from_tty); } /* Record log save-file format Version 1 Header: 4 bytes: magic number htonl(0x20090726). NOTE: be sure to change whenever this file format changes! Records: record_end: 1 byte: record type (record_end). record_reg: 1 byte: record type (record_reg). 8 bytes: register id (network byte order). MAX_REGISTER_SIZE bytes: register value. record_mem: 1 byte: record type (record_mem). 8 bytes: memory address (network byte order). 8 bytes: memory length (network byte order). n bytes: memory value (n == memory length). */ /* bfdcore_write -- write bytes into a core file section. */ static int bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset) { int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len); if (ret) *offset += len; return ret; } /* Dump the execution log to a file. */ static void cmd_record_dump (char *args, int from_tty) { char *recfilename, recfilename_buffer[40]; int recfd; struct record_entry *cur_record_list; uint32_t magic; struct regcache *regcache; struct gdbarch *gdbarch; struct cleanup *old_cleanups; struct cleanup *set_cleanups; bfd *obfd; int dump_size = 0; asection *osec = NULL; struct record_entry *p; int bfd_offset = 0; if (current_target.to_stratum != record_stratum) error (_("Process record is not started.\n")); if (args && *args) recfilename = args; else { /* Default recfile name is "rec.PID". */ snprintf (recfilename_buffer, sizeof (recfilename_buffer), "gdb_record.%d", PIDGET (inferior_ptid)); recfilename = recfilename_buffer; } /* Open the dump file. */ if (record_debug) fprintf_filtered (gdb_stdlog, _("Saving recording to file '%s'\n"), recfilename); /* Open the output file. */ obfd = create_gcore_bfd (recfilename); /* Need a cleanup that will close the file (FIXME: delete it?). */ old_cleanups = make_cleanup_bfd_close (obfd); /* Save the current record entry to "cur_record_list". */ cur_record_list = record_list; /* Get the values of regcache and gdbarch. */ regcache = get_current_regcache (); gdbarch = get_regcache_arch (regcache); /* Disable the GDB operation record. */ set_cleanups = record_gdb_operation_disable_set (); /* Reverse execute to the begin of record list. */ for (; record_list && record_list != &record_first; record_list = record_list->prev) record_exec_entry (regcache, gdbarch, record_list); /* Compute the size needed for the extra bfd section. */ dump_size = 4; /* magic cookie */ for (p = &record_first; p; p = p->next) switch (p->type) { case record_end: dump_size += 1; break; case record_reg: dump_size += 1 + 8 + MAX_REGISTER_SIZE; break; case record_mem: dump_size += 1 + 8 + 8 + p->u.mem.len; break; } /* Make the new bfd section. */ osec = bfd_make_section_anyway (obfd, "precord"); bfd_set_section_size (obfd, osec, dump_size); bfd_set_section_vma (obfd, osec, 0); bfd_section_lma (obfd, osec) = 0; bfd_set_section_flags (obfd, osec, SEC_ALLOC | SEC_HAS_CONTENTS); /* Save corefile state. */ write_gcore_file (obfd); /* Write out the record log (modified Hui method). */ /* Write the magic code. */ magic = RECORD_FILE_MAGIC; if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), magic); if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset)) error (_("Failed to write 'magic' to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); /* Dump the entries into the new bfd section. */ for (p = &record_first; p; p = p->next) { uint8_t tmpu8; uint64_t tmpu64; tmpu8 = p->type; if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset)) error (_("Failed to write 'type' to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); switch (p->type) { case record_reg: /* reg */ tmpu64 = p->u.reg.num; if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Writing register %d val 0x%016llx (1 plus 8 plus %d bytes)\n"), p->u.reg.num, *(ULONGEST *) p->u.reg.val, MAX_REGISTER_SIZE); /* FIXME: register num does not need 8 bytes. */ if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to write regnum to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); /* FIXME: add a len field, and write the smaller value. */ if (!bfdcore_write (obfd, osec, p->u.reg.val, MAX_REGISTER_SIZE, &bfd_offset)) error (_("Failed to write regval to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); break; case record_mem: /* mem */ tmpu64 = p->u.mem.addr; if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Writing memory 0x%08x (1 plus 8 plus 8 bytes plus %d bytes)\n"), (unsigned int) p->u.mem.addr, p->u.mem.len); if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to write memaddr to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); tmpu64 = p->u.mem.len; if (BYTE_ORDER == LITTLE_ENDIAN) tmpu64 = bswap_64 (tmpu64); /* FIXME: len does not need 8 bytes. */ if (!bfdcore_write (obfd, osec, &tmpu64, sizeof (tmpu64), &bfd_offset)) error (_("Failed to write memlen to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); if (!bfdcore_write (obfd, osec, p->u.mem.val, p->u.mem.len, &bfd_offset)) error (_("Failed to write memval to %s (%s)"), recfilename, bfd_errmsg (bfd_get_error ())); break; case record_end: /* FIXME: record the contents of record_end rec. */ if (record_debug) fprintf_filtered (gdb_stdlog, _("\ Writing record_end (1 byte)\n")); break; } } /* Now forward-execute back to the saved entry. */ for (record_list = &record_first; record_list && record_list != cur_record_list; record_list = record_list->next) record_exec_entry (regcache, gdbarch, record_list); /* Clean-ups will close the output file and free malloc memory. */ do_cleanups (old_cleanups); /* Succeeded. */ fprintf_filtered (gdb_stdout, "Saved recfile %s.\n", recfilename); } /* Truncate the record log from the present point of replay until the end. */ static void cmd_record_delete (char *args, int from_tty) { if (current_target.to_stratum == record_stratum) { if (RECORD_IS_REPLAY) { if (!from_tty || query (_("Delete the log from this point forward " "and begin to record the running message " "at current PC?"))) record_list_release_next (); } else printf_unfiltered (_("Already at end of record list.\n")); } else printf_unfiltered (_("Process record is not started.\n")); } /* Implement the "stoprecord" command. */ static void cmd_record_stop (char *args, int from_tty) { if (current_target.to_stratum == record_stratum) { if (!record_list || !from_tty || query (_("Delete recorded log and " "stop recording?"))) { if (strcmp (current_target.to_shortname, "record") == 0) unpush_target (&record_ops); else unpush_target (&record_core_ops); } } else printf_unfiltered (_("Process record is not started.\n")); } /* Set upper limit of record log size. */ static void set_record_insn_max_num (char *args, int from_tty, struct cmd_list_element *c) { if (record_insn_num > record_insn_max_num && record_insn_max_num) { printf_unfiltered (_("Record instructions number is bigger than " "record instructions max number. Auto delete " "the first ones?\n")); while (record_insn_num > record_insn_max_num) record_list_release_first_insn (); } } /* Print the current index into the record log (number of insns recorded so far). */ static void show_record_insn_number (char *ignore, int from_tty) { printf_unfiltered (_("Record instruction number is %d.\n"), record_insn_num); } static struct cmd_list_element *record_cmdlist, *set_record_cmdlist, *show_record_cmdlist, *info_record_cmdlist; static void set_record_command (char *args, int from_tty) { printf_unfiltered (_("\ \"set record\" must be followed by an apporpriate subcommand.\n")); help_list (set_record_cmdlist, "set record ", all_commands, gdb_stdout); } static void show_record_command (char *args, int from_tty) { cmd_show_list (show_record_cmdlist, from_tty, ""); } static void info_record_command (char *args, int from_tty) { cmd_show_list (info_record_cmdlist, from_tty, ""); } void _initialize_record (void) { struct cmd_list_element *c; /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; record_first.type = record_end; init_record_ops (); add_target (&record_ops); init_record_core_ops (); add_target (&record_core_ops); add_setshow_zinteger_cmd ("record", no_class, &record_debug, _("Set debugging of record/replay feature."), _("Show debugging of record/replay feature."), _("When enabled, debugging output for " "record/replay feature is displayed."), NULL, show_record_debug, &setdebuglist, &showdebuglist); c = add_prefix_cmd ("record", class_obscure, cmd_record_start, _("Abbreviated form of \"target record\" command."), &record_cmdlist, "record ", 0, &cmdlist); set_cmd_completer (c, filename_completer); add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, "set record ", 0, &setlist); add_alias_cmd ("rec", "record", class_obscure, 1, &setlist); add_prefix_cmd ("record", class_support, show_record_command, _("Show record options"), &show_record_cmdlist, "show record ", 0, &showlist); add_alias_cmd ("rec", "record", class_obscure, 1, &showlist); add_prefix_cmd ("record", class_support, info_record_command, _("Info record options"), &info_record_cmdlist, "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); c = add_cmd ("load", class_obscure, cmd_record_load, _("Load previously dumped execution records from \ a file given as argument."), &record_cmdlist); set_cmd_completer (c, filename_completer); c = add_cmd ("dump", class_obscure, cmd_record_dump, _("Dump the execution records to a file.\n\ Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), &record_cmdlist); set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), &record_cmdlist); add_alias_cmd ("d", "delete", class_obscure, 1, &record_cmdlist); add_alias_cmd ("del", "delete", class_obscure, 1, &record_cmdlist); add_cmd ("stop", class_obscure, cmd_record_stop, _("Stop the record/replay target."), &record_cmdlist); add_alias_cmd ("s", "stop", class_obscure, 1, &record_cmdlist); /* Record instructions number limit command. */ add_setshow_boolean_cmd ("stop-at-limit", no_class, &record_stop_at_limit, _("\ Set whether record/replay stops when record/replay buffer becomes full."), _("\ Show whether record/replay stops when record/replay buffer becomes full."), _("\ Default is ON.\n\ When ON, if the record/replay buffer becomes full, ask user what to do.\n\ When OFF, if the record/replay buffer becomes full,\n\ delete the oldest recorded instruction to make room for each new one."), NULL, NULL, &set_record_cmdlist, &show_record_cmdlist); add_setshow_zinteger_cmd ("insn-number-max", no_class, &record_insn_max_num, _("Set record/replay buffer limit."), _("Show record/replay buffer limit."), _("\ Set the maximum number of instructions to be stored in the\n\ record/replay buffer. Zero means unlimited. Default is 200000."), set_record_insn_max_num, NULL, &set_record_cmdlist, &show_record_cmdlist); add_cmd ("insn-number", class_obscure, show_record_insn_number, _("Show the current number of instructions in the " "record/replay buffer."), &info_record_cmdlist); } ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-26 1:40 ` Michael Snyder @ 2009-08-26 2:59 ` Michael Snyder 2009-08-29 15:53 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-26 2:59 UTC (permalink / raw) To: Michael Snyder; +Cc: Hui Zhu, Eli Zaretskii, gdb-patches Michael Snyder wrote: > Hui Zhu wrote: >> On Tue, Aug 25, 2009 at 01:51, Michael Snyder<msnyder@vmware.com> wrote: >>> Have you considered combining this patch with the idea that I >>> proposed -- storing the record log in the core file? Seems like >>> that would be very easy now -- gdb already has the core file open, >>> and there is a global pointer to the BFD. >>> See gdbcore.h:extern bfd* core_bfd; >> Cool. If we just want record_core_ops support load, we can remove the >> cmd "record load". :) >> >>> In fact, just for fun, I've merged the two patches in my >>> local tree. It seems to work quite well. I can just post >>> it here if you like, or here's a hint to show you how it goes: >> >> Why not? Please post it. I can not wait to try it. :) > > Great -- attached. This is "quick and dirty", by the way. It's not meant to be finished! ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-26 2:59 ` Michael Snyder @ 2009-08-29 15:53 ` Hui Zhu 2009-08-29 18:06 ` Eli Zaretskii 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-29 15:53 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches [-- Attachment #1: Type: text/plain, Size: 62632 bytes --] On Wed, Aug 26, 2009 at 09:35, Michael Snyder<msnyder@vmware.com> wrote: > Michael Snyder wrote: >> >> Hui Zhu wrote: >>> >>> On Tue, Aug 25, 2009 at 01:51, Michael Snyder<msnyder@vmware.com> wrote: >>>> >>>> Have you considered combining this patch with the idea that I >>>> proposed -- storing the record log in the core file? Seems like >>>> that would be very easy now -- gdb already has the core file open, >>>> and there is a global pointer to the BFD. >>>> See gdbcore.h:extern bfd* core_bfd; >>> >>> Cool. If we just want record_core_ops support load, we can remove the >>> cmd "record load". :) >>> >>>> In fact, just for fun, I've merged the two patches in my >>>> local tree. It seems to work quite well. I can just post >>>> it here if you like, or here's a hint to show you how it goes: >>> >>> Why not? Please post it. I can not wait to try it. :) >> >> Great -- attached. > > This is "quick and dirty", by the way. > It's not meant to be finished! > > Hi Michael, I make a new patch that make the patches together. Prec can load the record log from a core file and debug it with core target. To use it, Call "record" after load the gdb_record.xxx file. Please help me review it. Thanks, Hui 2009-08-29 Michael Snyder <msnyder@vmware.com> Hui Zhu <teawater@gmail.com> Add dump and load command to process record and replay. * record.c (completer.h, arch-utils.h, gdbcore.h, exec.h, elf-bfd.h, gcore.h, byteswap.h, netinet/in.h): Include files. (RECORD_FILE_MAGIC): New macro. (record_core_buf_entry): New struct. (record_core_ops): New target_ops. (record_list_release_first_insn): Change function record_list_release_first to this name. (record_reg_alloc, record_mem_alloc, record_end_alloc): New functions. (record_arch_list_add_reg): Call record_reg_alloc. (record_arch_list_add_mem): Call record_mem_alloc. (record_arch_list_add_end): Call record_end_alloc. (record_arch_list_cleanups): New function. (record_message_cleanups): Removed. (record_message): Change to call record_arch_list_cleanups and record_list_release_first_insn. (record_exec_entry, (bfdcore_read, record_load, record_core_open_1, record_open_1): New function. (record_open): Add support for record_core_ops. (record_close): Add support for record_core_ops. (record_wait): Call function 'record_exec_entry' and add support for target core. (record_registers_change): Call record_list_release_first_insn. (record_core_resume, record_core_resume, record_core_kill, record_core_fetch_registers, record_core_prepare_to_store, record_core_store_registers, record_core_xfer_partial, record_core_insert_breakpoint, record_core_remove_breakpoint, record_core_has_execution, init_record_core_ops, cmd_record_load, bfdcore_write, cmd_record_dump): New function. (cmd_record_stop): Add support for record_core_ops. (set_record_insn_max_num): Call record_list_release_first_insn. (_initialize_record): Add commands "record dump". * gcore.c (create_gcore_bfd): New function, abstracted from gcore_command for export. (write_gcore_file): New function, abstracted from gcore_command for export. (gcore_command): Call helper functions (above). (call_target_sbrk): New function, abstracted from derive_heap_segment. (derive_heap_segment): Call helper function (above). (load_core_segments): New function. (load_corefile): New function. * gcore.h: New file. 2009-08-29 Hui Zhu <teawater@gmail.com> * gdb.texinfo (Process Record and Replay): Document the "record dump" commands. --- gcore.c | 309 ++++++++++++++---- gcore.h | 23 + record.c | 1072 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 1199 insertions(+), 205 deletions(-) --- a/gcore.c +++ b/gcore.c @@ -25,10 +25,14 @@ #include "gdbcore.h" #include "objfiles.h" #include "symfile.h" - +#include "arch-utils.h" +#include "completer.h" +#include "gcore.h" #include "cli/cli-decode.h" - #include "gdb_assert.h" +#include <fcntl.h> +#include "regcache.h" +#include "regset.h" /* The largest amount of memory to read from the target at once. We must throttle it to limit the amount of memory used by GDB during @@ -40,45 +44,27 @@ static enum bfd_architecture default_gco static unsigned long default_gcore_mach (void); static int gcore_memory_sections (bfd *); -/* Generate a core file from the inferior process. */ +/* create_gcore_bfd -- helper for gcore_command (exported). */ -static void -gcore_command (char *args, int from_tty) +bfd * +create_gcore_bfd (char *filename) { - struct cleanup *old_chain; - char *corefilename, corefilename_buffer[40]; - asection *note_sec = NULL; - bfd *obfd; - void *note_data = NULL; - int note_size = 0; - - /* No use generating a corefile without a target process. */ - if (!target_has_execution) - noprocess (); - - if (args && *args) - corefilename = args; - else - { - /* Default corefile name is "core.PID". */ - sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid)); - corefilename = corefilename_buffer; - } - - if (info_verbose) - fprintf_filtered (gdb_stdout, - "Opening corefile '%s' for output.\n", corefilename); - - /* Open the output file. */ - obfd = bfd_openw (corefilename, default_gcore_target ()); + bfd *obfd = bfd_openw (filename, default_gcore_target ()); if (!obfd) - error (_("Failed to open '%s' for output."), corefilename); - - /* Need a cleanup that will close the file (FIXME: delete it?). */ - old_chain = make_cleanup_bfd_close (obfd); - + error (_("Failed to open '%s' for output."), filename); bfd_set_format (obfd, bfd_core); bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ()); + return obfd; +} + +/* write_gcore_file -- helper for gcore_command (exported). */ + +void +write_gcore_file (bfd *obfd) +{ + void *note_data = NULL; + int note_size = 0; + asection *note_sec = NULL; /* An external target method must build the notes section. */ note_data = target_make_corefile_notes (obfd, ¬e_size); @@ -107,8 +93,46 @@ gcore_command (char *args, int from_tty) if (note_data != NULL && note_size != 0) { if (!bfd_set_section_contents (obfd, note_sec, note_data, 0, note_size)) - warning (_("writing note section (%s)"), bfd_errmsg (bfd_get_error ())); + warning (_("writing note section (%s)"), + bfd_errmsg (bfd_get_error ())); } +} + +/* gcore_command -- implements the 'gcore' command. + Generate a core file from the inferior process. */ + +static void +gcore_command (char *args, int from_tty) +{ + struct cleanup *old_chain; + char *corefilename, corefilename_buffer[40]; + bfd *obfd; + + /* No use generating a corefile without a target process. */ + if (!target_has_execution) + noprocess (); + + if (args && *args) + corefilename = args; + else + { + /* Default corefile name is "core.PID". */ + sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid)); + corefilename = corefilename_buffer; + } + + if (info_verbose) + fprintf_filtered (gdb_stdout, + "Opening corefile '%s' for output.\n", corefilename); + + /* Open the output file. */ + obfd = create_gcore_bfd (corefilename); + + /* Need a cleanup that will close the file (FIXME: delete it?). */ + old_chain = make_cleanup_bfd_close (obfd); + + /* Call worker function. */ + write_gcore_file (obfd); /* Succeeded. */ fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename); @@ -212,6 +236,50 @@ derive_stack_segment (bfd_vma *bottom, b return 1; } +/* call_target_sbrk -- + helper function for derive_heap_segment and load_core_segment. */ + +static bfd_vma +call_target_sbrk (int sbrk_arg) +{ + struct objfile *sbrk_objf; + struct gdbarch *gdbarch; + bfd_vma top_of_heap; + struct value *target_sbrk_arg; + struct value *sbrk_fn, *ret; + bfd_vma tmp; + + if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL) + { + sbrk_fn = find_function_in_inferior ("sbrk", &sbrk_objf); + if (sbrk_fn == NULL) + return (bfd_vma) 0; + } + else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL) + { + sbrk_fn = find_function_in_inferior ("_sbrk", &sbrk_objf); + if (sbrk_fn == NULL) + return (bfd_vma) 0; + } + else + return (bfd_vma) 0; + + gdbarch = get_objfile_arch (sbrk_objf); + target_sbrk_arg = value_from_longest (builtin_type (gdbarch)->builtin_int, + sbrk_arg); + gdb_assert (target_sbrk_arg); + ret = call_function_by_hand (sbrk_fn, 1, &target_sbrk_arg); + if (ret == NULL) + return (bfd_vma) 0; + + tmp = value_as_long (ret); + if ((LONGEST) tmp <= 0 || (LONGEST) tmp == 0xffffffff) + return (bfd_vma) 0; + + top_of_heap = tmp; + return top_of_heap; +} + /* Derive a reasonable heap segment for ABFD by looking at sbrk and the static data sections. Store its limits in *BOTTOM and *TOP. Return non-zero if successful. */ @@ -219,12 +287,10 @@ derive_stack_segment (bfd_vma *bottom, b static int derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top) { - struct objfile *sbrk_objf; struct gdbarch *gdbarch; bfd_vma top_of_data_memory = 0; bfd_vma top_of_heap = 0; bfd_size_type sec_size; - struct value *zero, *sbrk; bfd_vma sec_vaddr; asection *sec; @@ -259,29 +325,9 @@ derive_heap_segment (bfd *abfd, bfd_vma } } - /* Now get the top-of-heap by calling sbrk in the inferior. */ - if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL) - { - sbrk = find_function_in_inferior ("sbrk", &sbrk_objf); - if (sbrk == NULL) - return 0; - } - else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL) - { - sbrk = find_function_in_inferior ("_sbrk", &sbrk_objf); - if (sbrk == NULL) - return 0; - } - else - return 0; - - gdbarch = get_objfile_arch (sbrk_objf); - zero = value_from_longest (builtin_type (gdbarch)->builtin_int, 0); - gdb_assert (zero); - sbrk = call_function_by_hand (sbrk, 1, &zero); - if (sbrk == NULL) + top_of_heap = call_target_sbrk (0); + if (top_of_heap == (bfd_vma) 0) return 0; - top_of_heap = value_as_long (sbrk); /* Return results. */ if (top_of_heap > top_of_data_memory) @@ -299,13 +345,15 @@ static void make_output_phdrs (bfd *obfd, asection *osec, void *ignored) { int p_flags = 0; - int p_type; + int p_type = 0; /* FIXME: these constants may only be applicable for ELF. */ if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0) p_type = PT_LOAD; - else + else if (strncmp (bfd_section_name (obfd, osec), "note", 4) == 0) p_type = PT_NOTE; + else + p_type = PT_NULL; p_flags |= PF_R; /* Segment is readable. */ if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY)) @@ -516,6 +564,141 @@ gcore_memory_sections (bfd *obfd) return 1; } +struct load_core_args_params { + int from_tty; + bfd_vma top_of_heap; +}; + +/* load_core_segments -- iterator function for bfd_map_over_sections. */ + +static void +load_core_segment (bfd *abfd, asection *asect, void *arg) +{ + struct load_core_args_params *params = arg; + struct cleanup *old_chain; + char *memhunk; + int ret; + + if ((bfd_section_size (abfd, asect) > 0) && + (bfd_get_section_flags (abfd, asect) & SEC_LOAD) && + !(bfd_get_section_flags (abfd, asect) & SEC_READONLY)) + { + if (info_verbose && params->from_tty) + { + printf_filtered (_("Load core section %s"), + bfd_section_name (abfd, asect)); + printf_filtered (_(", vma 0x%08lx to 0x%08lx"), + (unsigned long) bfd_section_vma (abfd, asect), + (unsigned long) bfd_section_vma (abfd, asect) + + (int) bfd_section_size (abfd, asect)); + printf_filtered (_(", size = %d"), + (int) bfd_section_size (abfd, asect)); + printf_filtered (_(".\n")); + } + /* Fixme cleanup? */ + memhunk = xmalloc (bfd_section_size (abfd, asect)); + bfd_get_section_contents (abfd, asect, memhunk, 0, + bfd_section_size (abfd, asect)); + if ((ret = target_write_memory (bfd_section_vma (abfd, asect), + memhunk, + bfd_section_size (abfd, asect))) != 0) + { + print_sys_errmsg ("load_core_segment", ret); + if ((LONGEST) params->top_of_heap < + (LONGEST) bfd_section_vma (abfd, asect) + + (LONGEST) bfd_section_size (abfd, asect)) + { + int increment = bfd_section_vma (abfd, asect) + + bfd_section_size (abfd, asect) - params->top_of_heap; + + params->top_of_heap = call_target_sbrk (increment); + if (params->top_of_heap == 0) + error ("sbrk failed, TOH = 0x%08lx", params->top_of_heap); + else + printf_filtered ("Increase TOH to 0x%08lx and retry.\n", + (unsigned long) params->top_of_heap); + if (target_write_memory (bfd_section_vma (abfd, asect), + memhunk, + bfd_section_size (abfd, asect)) != 0) + { + error ("Nope, still failed."); + } + } + } + xfree (memhunk); + } +} + +/* load_corefile -- reads a corefile, copies its memory sections + into target memory, and its registers into target regcache. */ + +bfd * +load_corefile (char *filename, int from_tty) +{ + struct load_core_args_params params; + struct bfd_section *regsect; + const struct regset *regset; + struct regcache *regcache; + struct cleanup *old_chain; + struct gdbarch *gdbarch; + char *scratch_path; + int scratch_chan; + char *contents; + bfd *core_bfd; + int size; + + scratch_chan = openp (getenv ("PATH"), OPF_TRY_CWD_FIRST, filename, + O_BINARY | O_RDONLY | O_LARGEFILE, &scratch_path); + if (scratch_chan < 0) + perror_with_name (filename); + + core_bfd = bfd_fdopenr (scratch_path, gnutarget, scratch_chan); + old_chain = make_cleanup_bfd_close (core_bfd); + if (!core_bfd) + perror_with_name (scratch_path); + + if (!bfd_check_format (core_bfd, bfd_core)) + error (_("\"%s\" is not a core file: %s"), + filename, bfd_errmsg (bfd_get_error ())); + + params.from_tty = from_tty; + params.top_of_heap = call_target_sbrk (0); + if (params.top_of_heap == 0) + error (_("Couldn't get sbrk.")); + + bfd_map_over_sections (core_bfd, load_core_segment, (void *) ¶ms); + /* Now need to get/set registers. */ + regsect = bfd_get_section_by_name (core_bfd, ".reg"); + + if (!regsect) + error (_("Couldn't find .reg section.")); + + size = bfd_section_size (core_bfd, regsect); + contents = alloca (size); + bfd_get_section_contents (core_bfd, regsect, contents, 0, size); + + /* See FIXME kettenis/20031023 comment in corelow.c */ + gdbarch = gdbarch_from_bfd (core_bfd); + + if (gdbarch && gdbarch_regset_from_core_section_p (gdbarch)) + { + regset = gdbarch_regset_from_core_section (gdbarch, ".reg", size); + if (!regset) + error (_("Failed to allocate regset.")); + + registers_changed (); + regcache = get_current_regcache (); + regset->supply_regset (regset, regcache, -1, contents, size); + reinit_frame_cache (); + target_store_registers (regcache, -1); + } + else + error (_("Failed to get regset from core section")); + + discard_cleanups (old_chain); + return core_bfd; +} + /* Provide a prototype to silence -Wmissing-prototypes. */ extern initialize_file_ftype _initialize_gcore; --- /dev/null +++ b/gcore.h @@ -0,0 +1,23 @@ +/* Support for reading/writing gcore files. + + Copyright (C) 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/>. */ + +extern bfd *create_gcore_bfd (char *filename); +extern void write_gcore_file (bfd *obfd); +extern bfd *load_corefile (char *filename, int from_tty); + --- a/record.c +++ b/record.c @@ -23,15 +23,25 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include "elf-bfd.h" +#include "gcore.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) +#define RECORD_FILE_MAGIC htonl(0x20090829) + /* These are the core struct of record function. An record_entry is a record of the value change of a register @@ -78,9 +88,22 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record buf with core target. */ +static gdb_byte *record_core_regbuf = NULL; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -94,6 +117,7 @@ static int record_insn_num = 0; /* The target_ops of process record. */ static struct target_ops record_ops; +static struct target_ops record_core_ops; /* The beneath function pointers. */ static struct target_ops *record_beneath_to_resume_ops; @@ -169,7 +193,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -227,6 +251,56 @@ record_arch_list_add (struct record_entr } } +/* Alloc a record_reg record entry. */ + +static inline struct record_entry * +record_reg_alloc (int num) +{ + struct record_entry *rec; + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + rec->u.reg.num = num; + + return rec; +} + +/* Alloc a record_mem record entry. */ + +static inline struct record_entry * +record_mem_alloc (int len) +{ + struct record_entry *rec; + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.mem.val = (gdb_byte *) xmalloc (len); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + rec->u.mem.len = len; + rec->u.mem.mem_entry_not_accessible = 0; + + return rec; +} + +/* Alloc a record_mem record entry. */ + +static inline struct record_entry * +record_end_alloc (void) +{ + struct record_entry *rec; + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + + return rec; +} + /* Record the value of a register NUM to record_arch_list. */ int @@ -240,12 +314,7 @@ record_arch_list_add_reg (struct regcach "record list.\n", num); - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_reg; - rec->u.reg.num = num; + rec = record_reg_alloc (num); regcache_raw_read (regcache, num, rec->u.reg.val); @@ -271,14 +340,8 @@ record_arch_list_add_mem (CORE_ADDR addr if (!addr) return 0; - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->u.mem.val = (gdb_byte *) xmalloc (len); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_mem; + rec = record_mem_alloc (len); rec->u.mem.addr = addr; - rec->u.mem.len = len; - rec->u.mem.mem_entry_not_accessible = 0; if (target_read_memory (addr, rec->u.mem.val, len)) { @@ -308,10 +371,7 @@ record_arch_list_add_end (void) fprintf_unfiltered (gdb_stdlog, "Process record: add end to arch list.\n"); - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_end; + rec = record_end_alloc (); record_arch_list_add (rec); @@ -340,30 +400,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +446,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,13 +476,297 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + } + break; + } +} + +/* Load the execution log from core_bfd. */ + +static int +bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset) +{ + int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len); + + if (ret) + *offset += len; + return ret; +} + static void -record_open (char *name, int from_tty) +record_load (void) { - struct target_ops *t; + uint32_t magic; + struct cleanup *old_cleanups; + struct record_entry *rec; + asection *osec; + int bfd_offset = 0; + + /* We load the execution log from the open core bfd, + if there is one. */ + if (core_bfd == NULL) + return; + + /* "record_load" just can be called when record list is empty. */ + gdb_assert (record_first.next == NULL); if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + fprintf_filtered (gdb_stdlog, + _("Restoring recording from core file.\n")); + + /* Now need to find our special note section. */ + osec = bfd_get_section_by_name (core_bfd, "null0"); + printf_filtered ("Find precord section %s.\n", + osec ? "succeeded" : "failed"); + if (!osec) + return; + if (record_debug) + fprintf_filtered (gdb_stdlog, "osec name = '%s'\n", + bfd_section_name (core_bfd, osec)); + + /* Check the magic code. */ + if (!bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset)) + error (_("Failed to read 'magic' from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + if (magic != RECORD_FILE_MAGIC) + error (_("Version mis-match or file format error.")); + if (record_debug) + fprintf_filtered (gdb_stdlog, _("\ +Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + record_insn_num = 0; + old_cleanups = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + /* FIXME: Check offset for end-of-section. */ + if (!bfdcore_read (core_bfd, osec, &tmpu8, + sizeof (tmpu8), &bfd_offset)) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + /* Get num to tmpu64. */ + if (!bfdcore_read (core_bfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to read regnum from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + + rec = record_reg_alloc ((int) tmpu64); + + /* Get val. */ + if (!bfdcore_read (core_bfd, osec, rec->u.reg.val, + MAX_REGISTER_SIZE, &bfd_offset)) + error (_("Failed to read regval from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading register %d (1 plus 8 plus %d bytes)\n"), + rec->u.reg.num, + MAX_REGISTER_SIZE); + break; + + case record_mem: /* mem */ + /* Get len. */ + if (!bfdcore_read (core_bfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to read memlen from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + + rec = record_mem_alloc ((int) tmpu64); + + /* Get addr. */ + if (!bfdcore_read (core_bfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to read memaddr from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + + /* Get val. */ + if (!bfdcore_read (core_bfd, osec, rec->u.mem.val, + rec->u.mem.len, &bfd_offset)) + error (_("Failed to read memval from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (get_current_arch (), + rec->u.mem.addr), + rec->u.mem.len); + break; + + case record_end: /* end */ + rec = record_end_alloc (); + record_insn_num ++; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Reading record_end (1 byte)\n")); + break; + + default: + error (_("Format of core file is not right.")); + break; + } + + /* Add rec to record arch list. */ + record_arch_list_add (rec); + } + + discard_cleanups (old_cleanups); + + /* Add record_arch_list_head to the end of record list. */ + record_first.next = record_arch_list_head; + record_arch_list_head->prev = &record_first; + + /* Update record_insn_max_num. */ + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded records from core file.\n"); + + print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); +} + +static struct target_ops *tmp_to_resume_ops; +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal); +static struct target_ops *tmp_to_wait_ops; +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *, + int); +static struct target_ops *tmp_to_store_registers_ops; +static void (*tmp_to_store_registers) (struct target_ops *, + struct regcache *, + int regno); +static struct target_ops *tmp_to_xfer_partial_ops; +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, + enum target_object object, + const char *annex, + gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, + LONGEST len); +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, + struct bp_target_info *); +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, + struct bp_target_info *); + +static void +record_core_open_1 (char *name, int from_tty) +{ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + /* Get record_core_regbuf. */ + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } + + push_target (&record_core_ops); +} + +static void +record_open_1 (char *name, int from_tty) +{ + struct target_ops *t; /* check exec */ if (!target_has_execution) @@ -438,6 +782,28 @@ record_open (char *name, int from_tty) error (_("Process record: the current architecture doesn't support " "record function.")); + if (!tmp_to_resume) + error (_("Process record can't get to_resume.")); + if (!tmp_to_wait) + error (_("Process record can't get to_wait.")); + if (!tmp_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!tmp_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!tmp_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + /* Check if record target is already running. */ if (current_target.to_stratum == record_stratum) { @@ -447,70 +813,102 @@ record_open (char *name, int from_tty) 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; + /* Reset the tmp beneath pointers. */ + tmp_to_resume_ops = NULL; + tmp_to_resume = NULL; + tmp_to_wait_ops = NULL; + tmp_to_wait = NULL; + tmp_to_store_registers_ops = NULL; + tmp_to_store_registers = NULL; + tmp_to_xfer_partial_ops = NULL; + tmp_to_xfer_partial = NULL; + tmp_to_insert_breakpoint = NULL; + tmp_to_remove_breakpoint = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) { - if (!record_beneath_to_resume) + if (!tmp_to_resume) { - record_beneath_to_resume = t->to_resume; - record_beneath_to_resume_ops = t; + tmp_to_resume = t->to_resume; + tmp_to_resume_ops = t; } - if (!record_beneath_to_wait) + if (!tmp_to_wait) { - record_beneath_to_wait = t->to_wait; - record_beneath_to_wait_ops = t; + tmp_to_wait = t->to_wait; + tmp_to_wait_ops = t; } - if (!record_beneath_to_store_registers) + if (!tmp_to_store_registers) { - record_beneath_to_store_registers = t->to_store_registers; - record_beneath_to_store_registers_ops = t; + tmp_to_store_registers = t->to_store_registers; + tmp_to_store_registers_ops = t; } - if (!record_beneath_to_xfer_partial) + if (!tmp_to_xfer_partial) { - record_beneath_to_xfer_partial = t->to_xfer_partial; - record_beneath_to_xfer_partial_ops = t; + tmp_to_xfer_partial = t->to_xfer_partial; + tmp_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 (!tmp_to_insert_breakpoint) + tmp_to_insert_breakpoint = t->to_insert_breakpoint; + if (!tmp_to_remove_breakpoint) + tmp_to_remove_breakpoint = t->to_remove_breakpoint; } - if (!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) + if (!tmp_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); + if (current_target.to_stratum == core_stratum) + record_core_open_1 (name, from_tty); + else + record_open_1 (name, from_tty); /* Reset */ record_insn_num = 0; record_list = &record_first; record_list->next = NULL; + + /* Set the tmp beneath pointers to beneath pointers. */ + record_beneath_to_resume_ops = tmp_to_resume_ops; + record_beneath_to_resume = tmp_to_resume; + record_beneath_to_wait_ops = tmp_to_wait_ops; + record_beneath_to_wait = tmp_to_wait; + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; + record_beneath_to_store_registers = tmp_to_store_registers; + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; + record_beneath_to_xfer_partial = tmp_to_xfer_partial; + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; + + if (strcmp (current_target.to_shortname, "record_core") == 0) + record_load (); } static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + if (record_core_regbuf) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + } + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -584,7 +982,7 @@ record_wait (struct target_ops *ops, "record_resume_step = %d\n", record_resume_step); - if (!RECORD_IS_REPLAY) + if (!RECORD_IS_REPLAY && ops != &record_core_ops) { if (record_resume_error) { @@ -712,76 +1110,9 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,6 +1232,7 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); + target_kill (); } @@ -945,7 +1277,7 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1058,7 +1390,7 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1138,6 +1470,191 @@ init_record_ops (void) } static void +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; +} + +static void +record_core_kill (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); + + unpush_target (&record_core_ops); +} + +static void +record_core_fetch_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); +} + +static void +record_core_prepare_to_store (struct regcache *regcache) +{ +} + +static void +record_core_store_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); +} + +static LONGEST +record_core_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, ULONGEST offset, + LONGEST len) +{ + if (object == TARGET_OBJECT_MEMORY) + { + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return -1; + } + else + error (_("You can't do that without a process to debug.")); + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +static int +record_core_insert_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +static int +record_core_remove_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +int +record_core_has_execution (struct target_ops *ops) +{ + return 1; +} + +static void +init_record_core_ops (void) +{ + record_core_ops.to_shortname = "record_core"; + record_core_ops.to_longname = "Process record and replay target"; + record_core_ops.to_doc = + "Log program while executing and replay execution from log."; + record_core_ops.to_open = record_open; + record_core_ops.to_close = record_close; + record_core_ops.to_resume = record_core_resume; + record_core_ops.to_wait = record_wait; + record_core_ops.to_kill = record_core_kill; + record_core_ops.to_fetch_registers = record_core_fetch_registers; + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; + record_core_ops.to_store_registers = record_core_store_registers; + record_core_ops.to_xfer_partial = record_core_xfer_partial; + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; + record_core_ops.to_has_execution = record_core_has_execution; + record_core_ops.to_stratum = record_stratum; + record_core_ops.to_magic = OPS_MAGIC; +} + +static void show_record_debug (struct ui_file *file, int from_tty, struct cmd_list_element *c, const char *value) { @@ -1153,6 +1670,262 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +/* Record log save-file format + Version 1 + + Header: + 4 bytes: magic number htonl(0x20090829). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end). + record_reg: + 1 byte: record type (record_reg). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem). + 8 bytes: memory length (network byte order). + 8 bytes: memory address (network byte order). + n bytes: memory value (n == memory length). +*/ + +/* bfdcore_write -- write bytes into a core file section. */ + +static int +bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset) +{ + int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len); + + if (ret) + *offset += len; + return ret; +} + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + bfd *obfd; + int dump_size = 0; + asection *osec = NULL; + int bfd_offset = 0; + + if (strcmp (current_target.to_shortname, "record") != 0) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default recfile name is "gdb_record.PID". */ + snprintf (recfilename_buffer, sizeof (recfilename_buffer), + "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Saving recording to file '%s'\n"), + recfilename); + /* Open the output file. */ + obfd = create_gcore_bfd (recfilename); + /* Need a cleanup that will close the file (FIXME: delete it?). */ + old_cleanups = make_cleanup_bfd_close (obfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Compute the size needed for the extra bfd section. */ + dump_size = 4; /* magic cookie */ + for (record_list = &record_first; record_list; + record_list = record_list->next) + switch (record_list->type) + { + case record_end: + dump_size += 1; + break; + case record_reg: + dump_size += 1 + 8 + MAX_REGISTER_SIZE; + break; + case record_mem: + dump_size += 1 + 8 + 8 + record_list->u.mem.len; + break; + } + + /* Make the new bfd section. */ + osec = bfd_make_section_anyway_with_flags (obfd, "precord", + SEC_HAS_CONTENTS + | SEC_READONLY); + if (osec == NULL) + error (_("Failed to create 'precord' section for corefile: %s"), + bfd_errmsg (bfd_get_error ())); + bfd_set_section_size (obfd, osec, dump_size); + bfd_set_section_vma (obfd, osec, 0); + bfd_set_section_alignment (obfd, osec, 0); + bfd_section_lma (obfd, osec) = 0; + + /* Save corefile state. */ + write_gcore_file (obfd); + + /* Write out the record log. */ + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_filtered (gdb_stdlog, _("\ +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset)) + error (_("Failed to write 'magic' to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + record_list = &record_first; + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8), + &bfd_offset)) + error (_("Failed to write 'type' to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing register %d (1 plus 8 plus %d bytes)\n"), + record_list->u.reg.num, + MAX_REGISTER_SIZE); + + /* Write regnum. */ + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + if (!bfdcore_write (obfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to write regnum to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + /* Write regval. */ + if (!bfdcore_write (obfd, osec, record_list->u.reg.val, + MAX_REGISTER_SIZE, &bfd_offset)) + error (_("Failed to write regval to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + break; + + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (gdbarch, + record_list->u.mem.addr), + record_list->u.mem.len); + + /* Write memlen. */ + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + if (!bfdcore_write (obfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to write memlen to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + /* Write memaddr. */ + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + if (!bfdcore_write (obfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to write memaddr to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + /* Write memval. */ + if (!bfdcore_write (obfd, osec, record_list->u.mem.val, + record_list->u.mem.len, &bfd_offset)) + error (_("Failed to write memval to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + } + break; + + case record_end: + /* FIXME: record the contents of record_end rec. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Writing record_end (1 byte)\n")); + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved recfile %s.\n"), recfilename); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1185,7 +1958,12 @@ cmd_record_stop (char *args, int from_tt { if (!record_list || !from_tty || query (_("Delete recorded log and " "stop recording?"))) - unpush_target (&record_ops); + { + if (strcmp (current_target.to_shortname, "record") == 0) + unpush_target (&record_ops); + else + unpush_target (&record_core_ops); + } } else printf_unfiltered (_("Process record is not started.\n")); @@ -1203,7 +1981,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1243,6 +2021,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1250,6 +2030,8 @@ _initialize_record (void) init_record_ops (); add_target (&record_ops); + init_record_core_ops (); + add_target (&record_core_ops); add_setshow_zinteger_cmd ("record", no_class, &record_debug, _("Set debugging of record/replay feature."), @@ -1259,9 +2041,10 @@ _initialize_record (void) NULL, show_record_debug, &setdebuglist, &showdebuglist); - add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), - &record_cmdlist, "record ", 0, &cmdlist); + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command."), + &record_cmdlist, "record ", 0, &cmdlist); + set_cmd_completer (c, filename_completer); add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, @@ -1276,6 +2059,11 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), --- doc/gdb.texinfo | 10 ++++++++++ 1 file changed, 10 insertions(+) --- a/doc/gdb.texinfo +++ b/doc/gdb.texinfo @@ -5214,6 +5214,16 @@ When record target runs in replay mode ( subsequent execution log and begin to record a new execution log starting from the current address. This means you will abandon the previously recorded ``future'' and begin recording a new ``future''. + +@kindex record dump +@kindex rec dump +@item record dump [@var{file}] +@itemx rec dump [@var{file}] +Dump the execution records of the inferior process to a core file. +The optional argument @var{file} specifies the file name where to put +the record dump. If not specified, the file name defaults +to @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the +inferior process. @end table [-- Attachment #2: prec-dump.txt --] [-- Type: text/plain, Size: 56516 bytes --] --- gcore.c | 309 ++++++++++++++---- gcore.h | 23 + record.c | 1072 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 1199 insertions(+), 205 deletions(-) --- a/gcore.c +++ b/gcore.c @@ -25,10 +25,14 @@ #include "gdbcore.h" #include "objfiles.h" #include "symfile.h" - +#include "arch-utils.h" +#include "completer.h" +#include "gcore.h" #include "cli/cli-decode.h" - #include "gdb_assert.h" +#include <fcntl.h> +#include "regcache.h" +#include "regset.h" /* The largest amount of memory to read from the target at once. We must throttle it to limit the amount of memory used by GDB during @@ -40,45 +44,27 @@ static enum bfd_architecture default_gco static unsigned long default_gcore_mach (void); static int gcore_memory_sections (bfd *); -/* Generate a core file from the inferior process. */ +/* create_gcore_bfd -- helper for gcore_command (exported). */ -static void -gcore_command (char *args, int from_tty) +bfd * +create_gcore_bfd (char *filename) { - struct cleanup *old_chain; - char *corefilename, corefilename_buffer[40]; - asection *note_sec = NULL; - bfd *obfd; - void *note_data = NULL; - int note_size = 0; - - /* No use generating a corefile without a target process. */ - if (!target_has_execution) - noprocess (); - - if (args && *args) - corefilename = args; - else - { - /* Default corefile name is "core.PID". */ - sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid)); - corefilename = corefilename_buffer; - } - - if (info_verbose) - fprintf_filtered (gdb_stdout, - "Opening corefile '%s' for output.\n", corefilename); - - /* Open the output file. */ - obfd = bfd_openw (corefilename, default_gcore_target ()); + bfd *obfd = bfd_openw (filename, default_gcore_target ()); if (!obfd) - error (_("Failed to open '%s' for output."), corefilename); - - /* Need a cleanup that will close the file (FIXME: delete it?). */ - old_chain = make_cleanup_bfd_close (obfd); - + error (_("Failed to open '%s' for output."), filename); bfd_set_format (obfd, bfd_core); bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ()); + return obfd; +} + +/* write_gcore_file -- helper for gcore_command (exported). */ + +void +write_gcore_file (bfd *obfd) +{ + void *note_data = NULL; + int note_size = 0; + asection *note_sec = NULL; /* An external target method must build the notes section. */ note_data = target_make_corefile_notes (obfd, ¬e_size); @@ -107,8 +93,46 @@ gcore_command (char *args, int from_tty) if (note_data != NULL && note_size != 0) { if (!bfd_set_section_contents (obfd, note_sec, note_data, 0, note_size)) - warning (_("writing note section (%s)"), bfd_errmsg (bfd_get_error ())); + warning (_("writing note section (%s)"), + bfd_errmsg (bfd_get_error ())); } +} + +/* gcore_command -- implements the 'gcore' command. + Generate a core file from the inferior process. */ + +static void +gcore_command (char *args, int from_tty) +{ + struct cleanup *old_chain; + char *corefilename, corefilename_buffer[40]; + bfd *obfd; + + /* No use generating a corefile without a target process. */ + if (!target_has_execution) + noprocess (); + + if (args && *args) + corefilename = args; + else + { + /* Default corefile name is "core.PID". */ + sprintf (corefilename_buffer, "core.%d", PIDGET (inferior_ptid)); + corefilename = corefilename_buffer; + } + + if (info_verbose) + fprintf_filtered (gdb_stdout, + "Opening corefile '%s' for output.\n", corefilename); + + /* Open the output file. */ + obfd = create_gcore_bfd (corefilename); + + /* Need a cleanup that will close the file (FIXME: delete it?). */ + old_chain = make_cleanup_bfd_close (obfd); + + /* Call worker function. */ + write_gcore_file (obfd); /* Succeeded. */ fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename); @@ -212,6 +236,50 @@ derive_stack_segment (bfd_vma *bottom, b return 1; } +/* call_target_sbrk -- + helper function for derive_heap_segment and load_core_segment. */ + +static bfd_vma +call_target_sbrk (int sbrk_arg) +{ + struct objfile *sbrk_objf; + struct gdbarch *gdbarch; + bfd_vma top_of_heap; + struct value *target_sbrk_arg; + struct value *sbrk_fn, *ret; + bfd_vma tmp; + + if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL) + { + sbrk_fn = find_function_in_inferior ("sbrk", &sbrk_objf); + if (sbrk_fn == NULL) + return (bfd_vma) 0; + } + else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL) + { + sbrk_fn = find_function_in_inferior ("_sbrk", &sbrk_objf); + if (sbrk_fn == NULL) + return (bfd_vma) 0; + } + else + return (bfd_vma) 0; + + gdbarch = get_objfile_arch (sbrk_objf); + target_sbrk_arg = value_from_longest (builtin_type (gdbarch)->builtin_int, + sbrk_arg); + gdb_assert (target_sbrk_arg); + ret = call_function_by_hand (sbrk_fn, 1, &target_sbrk_arg); + if (ret == NULL) + return (bfd_vma) 0; + + tmp = value_as_long (ret); + if ((LONGEST) tmp <= 0 || (LONGEST) tmp == 0xffffffff) + return (bfd_vma) 0; + + top_of_heap = tmp; + return top_of_heap; +} + /* Derive a reasonable heap segment for ABFD by looking at sbrk and the static data sections. Store its limits in *BOTTOM and *TOP. Return non-zero if successful. */ @@ -219,12 +287,10 @@ derive_stack_segment (bfd_vma *bottom, b static int derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top) { - struct objfile *sbrk_objf; struct gdbarch *gdbarch; bfd_vma top_of_data_memory = 0; bfd_vma top_of_heap = 0; bfd_size_type sec_size; - struct value *zero, *sbrk; bfd_vma sec_vaddr; asection *sec; @@ -259,29 +325,9 @@ derive_heap_segment (bfd *abfd, bfd_vma } } - /* Now get the top-of-heap by calling sbrk in the inferior. */ - if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL) - { - sbrk = find_function_in_inferior ("sbrk", &sbrk_objf); - if (sbrk == NULL) - return 0; - } - else if (lookup_minimal_symbol ("_sbrk", NULL, NULL) != NULL) - { - sbrk = find_function_in_inferior ("_sbrk", &sbrk_objf); - if (sbrk == NULL) - return 0; - } - else - return 0; - - gdbarch = get_objfile_arch (sbrk_objf); - zero = value_from_longest (builtin_type (gdbarch)->builtin_int, 0); - gdb_assert (zero); - sbrk = call_function_by_hand (sbrk, 1, &zero); - if (sbrk == NULL) + top_of_heap = call_target_sbrk (0); + if (top_of_heap == (bfd_vma) 0) return 0; - top_of_heap = value_as_long (sbrk); /* Return results. */ if (top_of_heap > top_of_data_memory) @@ -299,13 +345,15 @@ static void make_output_phdrs (bfd *obfd, asection *osec, void *ignored) { int p_flags = 0; - int p_type; + int p_type = 0; /* FIXME: these constants may only be applicable for ELF. */ if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0) p_type = PT_LOAD; - else + else if (strncmp (bfd_section_name (obfd, osec), "note", 4) == 0) p_type = PT_NOTE; + else + p_type = PT_NULL; p_flags |= PF_R; /* Segment is readable. */ if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY)) @@ -516,6 +564,141 @@ gcore_memory_sections (bfd *obfd) return 1; } +struct load_core_args_params { + int from_tty; + bfd_vma top_of_heap; +}; + +/* load_core_segments -- iterator function for bfd_map_over_sections. */ + +static void +load_core_segment (bfd *abfd, asection *asect, void *arg) +{ + struct load_core_args_params *params = arg; + struct cleanup *old_chain; + char *memhunk; + int ret; + + if ((bfd_section_size (abfd, asect) > 0) && + (bfd_get_section_flags (abfd, asect) & SEC_LOAD) && + !(bfd_get_section_flags (abfd, asect) & SEC_READONLY)) + { + if (info_verbose && params->from_tty) + { + printf_filtered (_("Load core section %s"), + bfd_section_name (abfd, asect)); + printf_filtered (_(", vma 0x%08lx to 0x%08lx"), + (unsigned long) bfd_section_vma (abfd, asect), + (unsigned long) bfd_section_vma (abfd, asect) + + (int) bfd_section_size (abfd, asect)); + printf_filtered (_(", size = %d"), + (int) bfd_section_size (abfd, asect)); + printf_filtered (_(".\n")); + } + /* Fixme cleanup? */ + memhunk = xmalloc (bfd_section_size (abfd, asect)); + bfd_get_section_contents (abfd, asect, memhunk, 0, + bfd_section_size (abfd, asect)); + if ((ret = target_write_memory (bfd_section_vma (abfd, asect), + memhunk, + bfd_section_size (abfd, asect))) != 0) + { + print_sys_errmsg ("load_core_segment", ret); + if ((LONGEST) params->top_of_heap < + (LONGEST) bfd_section_vma (abfd, asect) + + (LONGEST) bfd_section_size (abfd, asect)) + { + int increment = bfd_section_vma (abfd, asect) + + bfd_section_size (abfd, asect) - params->top_of_heap; + + params->top_of_heap = call_target_sbrk (increment); + if (params->top_of_heap == 0) + error ("sbrk failed, TOH = 0x%08lx", params->top_of_heap); + else + printf_filtered ("Increase TOH to 0x%08lx and retry.\n", + (unsigned long) params->top_of_heap); + if (target_write_memory (bfd_section_vma (abfd, asect), + memhunk, + bfd_section_size (abfd, asect)) != 0) + { + error ("Nope, still failed."); + } + } + } + xfree (memhunk); + } +} + +/* load_corefile -- reads a corefile, copies its memory sections + into target memory, and its registers into target regcache. */ + +bfd * +load_corefile (char *filename, int from_tty) +{ + struct load_core_args_params params; + struct bfd_section *regsect; + const struct regset *regset; + struct regcache *regcache; + struct cleanup *old_chain; + struct gdbarch *gdbarch; + char *scratch_path; + int scratch_chan; + char *contents; + bfd *core_bfd; + int size; + + scratch_chan = openp (getenv ("PATH"), OPF_TRY_CWD_FIRST, filename, + O_BINARY | O_RDONLY | O_LARGEFILE, &scratch_path); + if (scratch_chan < 0) + perror_with_name (filename); + + core_bfd = bfd_fdopenr (scratch_path, gnutarget, scratch_chan); + old_chain = make_cleanup_bfd_close (core_bfd); + if (!core_bfd) + perror_with_name (scratch_path); + + if (!bfd_check_format (core_bfd, bfd_core)) + error (_("\"%s\" is not a core file: %s"), + filename, bfd_errmsg (bfd_get_error ())); + + params.from_tty = from_tty; + params.top_of_heap = call_target_sbrk (0); + if (params.top_of_heap == 0) + error (_("Couldn't get sbrk.")); + + bfd_map_over_sections (core_bfd, load_core_segment, (void *) ¶ms); + /* Now need to get/set registers. */ + regsect = bfd_get_section_by_name (core_bfd, ".reg"); + + if (!regsect) + error (_("Couldn't find .reg section.")); + + size = bfd_section_size (core_bfd, regsect); + contents = alloca (size); + bfd_get_section_contents (core_bfd, regsect, contents, 0, size); + + /* See FIXME kettenis/20031023 comment in corelow.c */ + gdbarch = gdbarch_from_bfd (core_bfd); + + if (gdbarch && gdbarch_regset_from_core_section_p (gdbarch)) + { + regset = gdbarch_regset_from_core_section (gdbarch, ".reg", size); + if (!regset) + error (_("Failed to allocate regset.")); + + registers_changed (); + regcache = get_current_regcache (); + regset->supply_regset (regset, regcache, -1, contents, size); + reinit_frame_cache (); + target_store_registers (regcache, -1); + } + else + error (_("Failed to get regset from core section")); + + discard_cleanups (old_chain); + return core_bfd; +} + /* Provide a prototype to silence -Wmissing-prototypes. */ extern initialize_file_ftype _initialize_gcore; --- /dev/null +++ b/gcore.h @@ -0,0 +1,23 @@ +/* Support for reading/writing gcore files. + + Copyright (C) 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/>. */ + +extern bfd *create_gcore_bfd (char *filename); +extern void write_gcore_file (bfd *obfd); +extern bfd *load_corefile (char *filename, int from_tty); + --- a/record.c +++ b/record.c @@ -23,15 +23,25 @@ #include "gdbthread.h" #include "event-top.h" #include "exceptions.h" +#include "completer.h" +#include "arch-utils.h" +#include "gdbcore.h" +#include "exec.h" #include "record.h" +#include "elf-bfd.h" +#include "gcore.h" +#include <byteswap.h> #include <signal.h> +#include <netinet/in.h> #define DEFAULT_RECORD_INSN_MAX_NUM 200000 #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) +#define RECORD_FILE_MAGIC htonl(0x20090829) + /* These are the core struct of record function. An record_entry is a record of the value change of a register @@ -78,9 +88,22 @@ struct record_entry } u; }; +struct record_core_buf_entry +{ + struct record_core_buf_entry *prev; + struct target_section *p; + bfd_byte *buf; +}; + /* This is the debug switch for process record. */ int record_debug = 0; +/* Record buf with core target. */ +static gdb_byte *record_core_regbuf = NULL; +static struct target_section *record_core_start; +static struct target_section *record_core_end; +static struct record_core_buf_entry *record_core_buf_list = NULL; + /* These list is for execution log. */ static struct record_entry record_first; static struct record_entry *record_list = &record_first; @@ -94,6 +117,7 @@ static int record_insn_num = 0; /* The target_ops of process record. */ static struct target_ops record_ops; +static struct target_ops record_core_ops; /* The beneath function pointers. */ static struct target_ops *record_beneath_to_resume_ops; @@ -169,7 +193,7 @@ record_list_release_next (void) } static void -record_list_release_first (void) +record_list_release_first_insn (void) { struct record_entry *tmp = NULL; enum record_type type; @@ -227,6 +251,56 @@ record_arch_list_add (struct record_entr } } +/* Alloc a record_reg record entry. */ + +static inline struct record_entry * +record_reg_alloc (int num) +{ + struct record_entry *rec; + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_reg; + rec->u.reg.num = num; + + return rec; +} + +/* Alloc a record_mem record entry. */ + +static inline struct record_entry * +record_mem_alloc (int len) +{ + struct record_entry *rec; + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->u.mem.val = (gdb_byte *) xmalloc (len); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_mem; + rec->u.mem.len = len; + rec->u.mem.mem_entry_not_accessible = 0; + + return rec; +} + +/* Alloc a record_mem record entry. */ + +static inline struct record_entry * +record_end_alloc (void) +{ + struct record_entry *rec; + + rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); + rec->prev = NULL; + rec->next = NULL; + rec->type = record_end; + + return rec; +} + /* Record the value of a register NUM to record_arch_list. */ int @@ -240,12 +314,7 @@ record_arch_list_add_reg (struct regcach "record list.\n", num); - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->u.reg.val = (gdb_byte *) xmalloc (MAX_REGISTER_SIZE); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_reg; - rec->u.reg.num = num; + rec = record_reg_alloc (num); regcache_raw_read (regcache, num, rec->u.reg.val); @@ -271,14 +340,8 @@ record_arch_list_add_mem (CORE_ADDR addr if (!addr) return 0; - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->u.mem.val = (gdb_byte *) xmalloc (len); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_mem; + rec = record_mem_alloc (len); rec->u.mem.addr = addr; - rec->u.mem.len = len; - rec->u.mem.mem_entry_not_accessible = 0; if (target_read_memory (addr, rec->u.mem.val, len)) { @@ -308,10 +371,7 @@ record_arch_list_add_end (void) fprintf_unfiltered (gdb_stdlog, "Process record: add end to arch list.\n"); - rec = (struct record_entry *) xmalloc (sizeof (struct record_entry)); - rec->prev = NULL; - rec->next = NULL; - rec->type = record_end; + rec = record_end_alloc (); record_arch_list_add (rec); @@ -340,30 +400,30 @@ record_check_insn_num (int set_terminal) if (q) record_stop_at_limit = 0; else - error (_("Process record: inferior program stopped.")); + error (_("Process record: stoped by user.")); } } } } +static void +record_arch_list_cleanups (void *ignore) +{ + record_list_release (record_arch_list_tail); +} + /* Before inferior step (when GDB record the running message, inferior only can step), GDB will call this function to record the values to record_list. This function will call gdbarch_process_record to record the running message of inferior and set them to record_arch_list, and add it to record_list. */ -static void -record_message_cleanups (void *ignore) -{ - record_list_release (record_arch_list_tail); -} - static int record_message (void *args) { int ret; struct regcache *regcache = args; - struct cleanup *old_cleanups = make_cleanup (record_message_cleanups, 0); + struct cleanup *old_cleanups = make_cleanup (record_arch_list_cleanups, 0); record_arch_list_head = NULL; record_arch_list_tail = NULL; @@ -386,7 +446,7 @@ record_message (void *args) record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; @@ -416,13 +476,297 @@ record_gdb_operation_disable_set (void) return old_cleanups; } +static inline void +record_exec_entry (struct regcache *regcache, struct gdbarch *gdbarch, + struct record_entry *entry) +{ + switch (entry->type) + { + case record_reg: /* reg */ + { + gdb_byte reg[MAX_REGISTER_SIZE]; + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_reg %s to " + "inferior num = %d.\n", + host_address_to_string (entry), + entry->u.reg.num); + + regcache_cooked_read (regcache, entry->u.reg.num, reg); + regcache_cooked_write (regcache, entry->u.reg.num, entry->u.reg.val); + memcpy (entry->u.reg.val, reg, MAX_REGISTER_SIZE); + } + break; + + case record_mem: /* mem */ + { + if (!record_list->u.mem.mem_entry_not_accessible) + { + gdb_byte *mem = alloca (entry->u.mem.len); + + if (record_debug > 1) + fprintf_unfiltered (gdb_stdlog, + "Process record: record_mem %s to " + "inferior addr = %s len = %d.\n", + host_address_to_string (entry), + paddress (gdbarch, entry->u.mem.addr), + record_list->u.mem.len); + + if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error reading memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + { + if (target_write_memory (entry->u.mem.addr, entry->u.mem.val, + entry->u.mem.len)) + { + record_list->u.mem.mem_entry_not_accessible = 1; + if (record_debug) + warning (_("Process record: error writing memory at " + "addr = %s len = %d."), + paddress (gdbarch, entry->u.mem.addr), + entry->u.mem.len); + } + else + memcpy (entry->u.mem.val, mem, entry->u.mem.len); + } + } + } + break; + } +} + +/* Load the execution log from core_bfd. */ + +static int +bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset) +{ + int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len); + + if (ret) + *offset += len; + return ret; +} + static void -record_open (char *name, int from_tty) +record_load (void) { - struct target_ops *t; + uint32_t magic; + struct cleanup *old_cleanups; + struct record_entry *rec; + asection *osec; + int bfd_offset = 0; + + /* We load the execution log from the open core bfd, + if there is one. */ + if (core_bfd == NULL) + return; + + /* "record_load" just can be called when record list is empty. */ + gdb_assert (record_first.next == NULL); if (record_debug) - fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + fprintf_filtered (gdb_stdlog, + _("Restoring recording from core file.\n")); + + /* Now need to find our special note section. */ + osec = bfd_get_section_by_name (core_bfd, "null0"); + printf_filtered ("Find precord section %s.\n", + osec ? "succeeded" : "failed"); + if (!osec) + return; + if (record_debug) + fprintf_filtered (gdb_stdlog, "osec name = '%s'\n", + bfd_section_name (core_bfd, osec)); + + /* Check the magic code. */ + if (!bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset)) + error (_("Failed to read 'magic' from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + if (magic != RECORD_FILE_MAGIC) + error (_("Version mis-match or file format error.")); + if (record_debug) + fprintf_filtered (gdb_stdlog, _("\ +Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + + /* Load the entries in recfd to the record_arch_list_head and + record_arch_list_tail. */ + record_arch_list_head = NULL; + record_arch_list_tail = NULL; + record_insn_num = 0; + old_cleanups = make_cleanup (record_arch_list_cleanups, 0); + + while (1) + { + int ret; + uint8_t tmpu8; + uint64_t tmpu64; + + /* FIXME: Check offset for end-of-section. */ + if (!bfdcore_read (core_bfd, osec, &tmpu8, + sizeof (tmpu8), &bfd_offset)) + break; + + switch (tmpu8) + { + case record_reg: /* reg */ + /* Get num to tmpu64. */ + if (!bfdcore_read (core_bfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to read regnum from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + + rec = record_reg_alloc ((int) tmpu64); + + /* Get val. */ + if (!bfdcore_read (core_bfd, osec, rec->u.reg.val, + MAX_REGISTER_SIZE, &bfd_offset)) + error (_("Failed to read regval from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading register %d (1 plus 8 plus %d bytes)\n"), + rec->u.reg.num, + MAX_REGISTER_SIZE); + break; + + case record_mem: /* mem */ + /* Get len. */ + if (!bfdcore_read (core_bfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to read memlen from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + + rec = record_mem_alloc ((int) tmpu64); + + /* Get addr. */ + if (!bfdcore_read (core_bfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to read memaddr from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + rec->u.mem.addr = tmpu64; + + /* Get val. */ + if (!bfdcore_read (core_bfd, osec, rec->u.mem.val, + rec->u.mem.len, &bfd_offset)) + error (_("Failed to read memval from core file (%s)"), + bfd_errmsg (bfd_get_error ())); + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Reading memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (get_current_arch (), + rec->u.mem.addr), + rec->u.mem.len); + break; + + case record_end: /* end */ + rec = record_end_alloc (); + record_insn_num ++; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Reading record_end (1 byte)\n")); + break; + + default: + error (_("Format of core file is not right.")); + break; + } + + /* Add rec to record arch list. */ + record_arch_list_add (rec); + } + + discard_cleanups (old_cleanups); + + /* Add record_arch_list_head to the end of record list. */ + record_first.next = record_arch_list_head; + record_arch_list_head->prev = &record_first; + + /* Update record_insn_max_num. */ + if (record_insn_num > record_insn_max_num) + { + record_insn_max_num = record_insn_num; + warning (_("Auto increase record/replay buffer limit to %d."), + record_insn_max_num); + } + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, "Loaded records from core file.\n"); + + print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC); +} + +static struct target_ops *tmp_to_resume_ops; +static void (*tmp_to_resume) (struct target_ops *, ptid_t, int, + enum target_signal); +static struct target_ops *tmp_to_wait_ops; +static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t, + struct target_waitstatus *, + int); +static struct target_ops *tmp_to_store_registers_ops; +static void (*tmp_to_store_registers) (struct target_ops *, + struct regcache *, + int regno); +static struct target_ops *tmp_to_xfer_partial_ops; +static LONGEST (*tmp_to_xfer_partial) (struct target_ops *ops, + enum target_object object, + const char *annex, + gdb_byte *readbuf, + const gdb_byte *writebuf, + ULONGEST offset, + LONGEST len); +static int (*tmp_to_insert_breakpoint) (struct gdbarch *, + struct bp_target_info *); +static int (*tmp_to_remove_breakpoint) (struct gdbarch *, + struct bp_target_info *); + +static void +record_core_open_1 (char *name, int from_tty) +{ + struct regcache *regcache = get_current_regcache (); + int regnum = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + /* Get record_core_regbuf. */ + target_fetch_registers (regcache, -1); + record_core_regbuf = xmalloc (MAX_REGISTER_SIZE * regnum); + for (i = 0; i < regnum; i ++) + regcache_raw_collect (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + + /* Get record_core_start and record_core_end. */ + if (build_section_table (core_bfd, &record_core_start, &record_core_end)) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + error (_("\"%s\": Can't find sections: %s"), + bfd_get_filename (core_bfd), bfd_errmsg (bfd_get_error ())); + } + + push_target (&record_core_ops); +} + +static void +record_open_1 (char *name, int from_tty) +{ + struct target_ops *t; /* check exec */ if (!target_has_execution) @@ -438,6 +782,28 @@ record_open (char *name, int from_tty) error (_("Process record: the current architecture doesn't support " "record function.")); + if (!tmp_to_resume) + error (_("Process record can't get to_resume.")); + if (!tmp_to_wait) + error (_("Process record can't get to_wait.")); + if (!tmp_to_store_registers) + error (_("Process record can't get to_store_registers.")); + if (!tmp_to_insert_breakpoint) + error (_("Process record can't get to_insert_breakpoint.")); + if (!tmp_to_remove_breakpoint) + error (_("Process record can't get to_remove_breakpoint.")); + + push_target (&record_ops); +} + +static void +record_open (char *name, int from_tty) +{ + struct target_ops *t; + + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_open\n"); + /* Check if record target is already running. */ if (current_target.to_stratum == record_stratum) { @@ -447,70 +813,102 @@ record_open (char *name, int from_tty) 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; + /* Reset the tmp beneath pointers. */ + tmp_to_resume_ops = NULL; + tmp_to_resume = NULL; + tmp_to_wait_ops = NULL; + tmp_to_wait = NULL; + tmp_to_store_registers_ops = NULL; + tmp_to_store_registers = NULL; + tmp_to_xfer_partial_ops = NULL; + tmp_to_xfer_partial = NULL; + tmp_to_insert_breakpoint = NULL; + tmp_to_remove_breakpoint = NULL; /* Set the beneath function pointers. */ for (t = current_target.beneath; t != NULL; t = t->beneath) { - if (!record_beneath_to_resume) + if (!tmp_to_resume) { - record_beneath_to_resume = t->to_resume; - record_beneath_to_resume_ops = t; + tmp_to_resume = t->to_resume; + tmp_to_resume_ops = t; } - if (!record_beneath_to_wait) + if (!tmp_to_wait) { - record_beneath_to_wait = t->to_wait; - record_beneath_to_wait_ops = t; + tmp_to_wait = t->to_wait; + tmp_to_wait_ops = t; } - if (!record_beneath_to_store_registers) + if (!tmp_to_store_registers) { - record_beneath_to_store_registers = t->to_store_registers; - record_beneath_to_store_registers_ops = t; + tmp_to_store_registers = t->to_store_registers; + tmp_to_store_registers_ops = t; } - if (!record_beneath_to_xfer_partial) + if (!tmp_to_xfer_partial) { - record_beneath_to_xfer_partial = t->to_xfer_partial; - record_beneath_to_xfer_partial_ops = t; + tmp_to_xfer_partial = t->to_xfer_partial; + tmp_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 (!tmp_to_insert_breakpoint) + tmp_to_insert_breakpoint = t->to_insert_breakpoint; + if (!tmp_to_remove_breakpoint) + tmp_to_remove_breakpoint = t->to_remove_breakpoint; } - if (!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) + if (!tmp_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); + if (current_target.to_stratum == core_stratum) + record_core_open_1 (name, from_tty); + else + record_open_1 (name, from_tty); /* Reset */ record_insn_num = 0; record_list = &record_first; record_list->next = NULL; + + /* Set the tmp beneath pointers to beneath pointers. */ + record_beneath_to_resume_ops = tmp_to_resume_ops; + record_beneath_to_resume = tmp_to_resume; + record_beneath_to_wait_ops = tmp_to_wait_ops; + record_beneath_to_wait = tmp_to_wait; + record_beneath_to_store_registers_ops = tmp_to_store_registers_ops; + record_beneath_to_store_registers = tmp_to_store_registers; + record_beneath_to_xfer_partial_ops = tmp_to_xfer_partial_ops; + record_beneath_to_xfer_partial = tmp_to_xfer_partial; + record_beneath_to_insert_breakpoint = tmp_to_insert_breakpoint; + record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint; + + if (strcmp (current_target.to_shortname, "record_core") == 0) + record_load (); } static void record_close (int quitting) { + struct record_core_buf_entry *entry; + if (record_debug) fprintf_unfiltered (gdb_stdlog, "Process record: record_close\n"); record_list_release (record_list); + + /* Release record_core_regbuf. */ + if (record_core_regbuf) + { + xfree (record_core_regbuf); + record_core_regbuf = NULL; + } + + /* Release record_core_buf_list. */ + if (record_core_buf_list) + { + for (entry = record_core_buf_list->prev; entry; entry = entry->prev) + { + xfree (record_core_buf_list); + record_core_buf_list = entry; + } + record_core_buf_list = NULL; + } } static int record_resume_step = 0; @@ -584,7 +982,7 @@ record_wait (struct target_ops *ops, "record_resume_step = %d\n", record_resume_step); - if (!RECORD_IS_REPLAY) + if (!RECORD_IS_REPLAY && ops != &record_core_ops) { if (record_resume_error) { @@ -712,76 +1110,9 @@ record_wait (struct target_ops *ops, 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 */ - /* Nothing to do if the entry is flagged not_accessible. */ - if (!record_list->u.mem.mem_entry_not_accessible) - { - 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 = %s len = %d.\n", - host_address_to_string (record_list), - paddress (gdbarch, - record_list->u.mem.addr), - record_list->u.mem.len); + record_exec_entry (regcache, gdbarch, record_list); - if (target_read_memory (record_list->u.mem.addr, mem, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error reading memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Read failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - if (target_write_memory (record_list->u.mem.addr, - record_list->u.mem.val, - record_list->u.mem.len)) - { - if (execution_direction != EXEC_REVERSE) - error (_("Process record: error writing memory at " - "addr = %s len = %d."), - paddress (gdbarch, record_list->u.mem.addr), - record_list->u.mem.len); - else - /* Write failed -- - flag entry as not_accessible. */ - record_list->u.mem.mem_entry_not_accessible = 1; - } - else - { - memcpy (record_list->u.mem.val, mem, - record_list->u.mem.len); - } - } - } - } - else + if (record_list->type == record_end) { if (record_debug > 1) fprintf_unfiltered (gdb_stdlog, @@ -901,6 +1232,7 @@ record_kill (struct target_ops *ops) fprintf_unfiltered (gdb_stdlog, "Process record: record_kill\n"); unpush_target (&record_ops); + target_kill (); } @@ -945,7 +1277,7 @@ record_registers_change (struct regcache record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1058,7 +1390,7 @@ record_xfer_partial (struct target_ops * record_list = record_arch_list_tail; if (record_insn_num == record_insn_max_num && record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); else record_insn_num++; } @@ -1138,6 +1470,191 @@ init_record_ops (void) } static void +record_core_resume (struct target_ops *ops, ptid_t ptid, int step, + enum target_signal siggnal) +{ + record_resume_step = step; + record_resume_siggnal = siggnal; +} + +static void +record_core_kill (struct target_ops *ops) +{ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, "Process record: record_core_kill\n"); + + unpush_target (&record_core_ops); +} + +static void +record_core_fetch_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (regno < 0) + { + int num = gdbarch_num_regs (get_regcache_arch (regcache)); + int i; + + for (i = 0; i < num; i ++) + regcache_raw_supply (regcache, i, + record_core_regbuf + MAX_REGISTER_SIZE * i); + } + else + regcache_raw_supply (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); +} + +static void +record_core_prepare_to_store (struct regcache *regcache) +{ +} + +static void +record_core_store_registers (struct target_ops *ops, + struct regcache *regcache, + int regno) +{ + if (record_gdb_operation_disable) + regcache_raw_collect (regcache, regno, + record_core_regbuf + MAX_REGISTER_SIZE * regno); + else + error (_("You can't do that without a process to debug.")); +} + +static LONGEST +record_core_xfer_partial (struct target_ops *ops, enum target_object object, + const char *annex, gdb_byte *readbuf, + const gdb_byte *writebuf, ULONGEST offset, + LONGEST len) +{ + if (object == TARGET_OBJECT_MEMORY) + { + if (record_gdb_operation_disable || !writebuf) + { + struct target_section *p; + for (p = record_core_start; p < record_core_end; p++) + { + if (offset >= p->addr) + { + struct record_core_buf_entry *entry; + + if (offset >= p->endaddr) + continue; + + if (offset + len > p->endaddr) + len = p->endaddr - offset; + + offset -= p->addr; + + /* Read readbuf or write writebuf p, offset, len. */ + /* Check flags. */ + if (p->the_bfd_section->flags & SEC_CONSTRUCTOR + || (p->the_bfd_section->flags & SEC_HAS_CONTENTS) == 0) + { + if (readbuf) + memset (readbuf, 0, len); + return len; + } + /* Get record_core_buf_entry. */ + for (entry = record_core_buf_list; entry; + entry = entry->prev) + if (entry->p == p) + break; + if (writebuf) + { + if (!entry) + { + /* Add a new entry. */ + entry + = (struct record_core_buf_entry *) + xmalloc + (sizeof (struct record_core_buf_entry)); + entry->p = p; + if (!bfd_malloc_and_get_section (p->bfd, + p->the_bfd_section, + &entry->buf)) + { + xfree (entry); + return 0; + } + entry->prev = record_core_buf_list; + record_core_buf_list = entry; + } + + memcpy (entry->buf + offset, writebuf, (size_t) len); + } + else + { + if (!entry) + return record_beneath_to_xfer_partial + (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); + + memcpy (readbuf, entry->buf + offset, (size_t) len); + } + + return len; + } + } + + return -1; + } + else + error (_("You can't do that without a process to debug.")); + } + + return record_beneath_to_xfer_partial (record_beneath_to_xfer_partial_ops, + object, annex, readbuf, writebuf, + offset, len); +} + +static int +record_core_insert_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +static int +record_core_remove_breakpoint (struct gdbarch *gdbarch, + struct bp_target_info *bp_tgt) +{ + return 0; +} + +int +record_core_has_execution (struct target_ops *ops) +{ + return 1; +} + +static void +init_record_core_ops (void) +{ + record_core_ops.to_shortname = "record_core"; + record_core_ops.to_longname = "Process record and replay target"; + record_core_ops.to_doc = + "Log program while executing and replay execution from log."; + record_core_ops.to_open = record_open; + record_core_ops.to_close = record_close; + record_core_ops.to_resume = record_core_resume; + record_core_ops.to_wait = record_wait; + record_core_ops.to_kill = record_core_kill; + record_core_ops.to_fetch_registers = record_core_fetch_registers; + record_core_ops.to_prepare_to_store = record_core_prepare_to_store; + record_core_ops.to_store_registers = record_core_store_registers; + record_core_ops.to_xfer_partial = record_core_xfer_partial; + record_core_ops.to_insert_breakpoint = record_core_insert_breakpoint; + record_core_ops.to_remove_breakpoint = record_core_remove_breakpoint; + record_core_ops.to_can_execute_reverse = record_can_execute_reverse; + record_core_ops.to_has_execution = record_core_has_execution; + record_core_ops.to_stratum = record_stratum; + record_core_ops.to_magic = OPS_MAGIC; +} + +static void show_record_debug (struct ui_file *file, int from_tty, struct cmd_list_element *c, const char *value) { @@ -1153,6 +1670,262 @@ cmd_record_start (char *args, int from_t execute_command ("target record", from_tty); } +/* Record log save-file format + Version 1 + + Header: + 4 bytes: magic number htonl(0x20090829). + NOTE: be sure to change whenever this file format changes! + + Records: + record_end: + 1 byte: record type (record_end). + record_reg: + 1 byte: record type (record_reg). + 8 bytes: register id (network byte order). + MAX_REGISTER_SIZE bytes: register value. + record_mem: + 1 byte: record type (record_mem). + 8 bytes: memory length (network byte order). + 8 bytes: memory address (network byte order). + n bytes: memory value (n == memory length). +*/ + +/* bfdcore_write -- write bytes into a core file section. */ + +static int +bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset) +{ + int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len); + + if (ret) + *offset += len; + return ret; +} + +/* Dump the execution log to a file. */ + +static void +cmd_record_dump (char *args, int from_tty) +{ + char *recfilename, recfilename_buffer[40]; + int recfd; + struct record_entry *cur_record_list; + uint32_t magic; + struct regcache *regcache; + struct gdbarch *gdbarch; + struct cleanup *old_cleanups; + struct cleanup *set_cleanups; + bfd *obfd; + int dump_size = 0; + asection *osec = NULL; + int bfd_offset = 0; + + if (strcmp (current_target.to_shortname, "record") != 0) + error (_("Process record is not started.\n")); + + if (args && *args) + recfilename = args; + else + { + /* Default recfile name is "gdb_record.PID". */ + snprintf (recfilename_buffer, sizeof (recfilename_buffer), + "gdb_record.%d", PIDGET (inferior_ptid)); + recfilename = recfilename_buffer; + } + + /* Open the dump file. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Saving recording to file '%s'\n"), + recfilename); + /* Open the output file. */ + obfd = create_gcore_bfd (recfilename); + /* Need a cleanup that will close the file (FIXME: delete it?). */ + old_cleanups = make_cleanup_bfd_close (obfd); + + /* Save the current record entry to "cur_record_list". */ + cur_record_list = record_list; + + /* Get the values of regcache and gdbarch. */ + regcache = get_current_regcache (); + gdbarch = get_regcache_arch (regcache); + + /* Disable the GDB operation record. */ + set_cleanups = record_gdb_operation_disable_set (); + + /* Reverse execute to the begin of record list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == &record_first) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + /* Compute the size needed for the extra bfd section. */ + dump_size = 4; /* magic cookie */ + for (record_list = &record_first; record_list; + record_list = record_list->next) + switch (record_list->type) + { + case record_end: + dump_size += 1; + break; + case record_reg: + dump_size += 1 + 8 + MAX_REGISTER_SIZE; + break; + case record_mem: + dump_size += 1 + 8 + 8 + record_list->u.mem.len; + break; + } + + /* Make the new bfd section. */ + osec = bfd_make_section_anyway_with_flags (obfd, "precord", + SEC_HAS_CONTENTS + | SEC_READONLY); + if (osec == NULL) + error (_("Failed to create 'precord' section for corefile: %s"), + bfd_errmsg (bfd_get_error ())); + bfd_set_section_size (obfd, osec, dump_size); + bfd_set_section_vma (obfd, osec, 0); + bfd_set_section_alignment (obfd, osec, 0); + bfd_section_lma (obfd, osec) = 0; + + /* Save corefile state. */ + write_gcore_file (obfd); + + /* Write out the record log. */ + /* Write the magic code. */ + magic = RECORD_FILE_MAGIC; + if (record_debug) + fprintf_filtered (gdb_stdlog, _("\ +Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n"), + magic); + if (!bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset)) + error (_("Failed to write 'magic' to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + /* Dump the entries to recfd and forward execute to the end of + record list. */ + record_list = &record_first; + while (1) + { + /* Dump entry. */ + if (record_list != &record_first) + { + uint8_t tmpu8; + uint64_t tmpu64; + + tmpu8 = record_list->type; + if (!bfdcore_write (obfd, osec, &tmpu8, sizeof (tmpu8), + &bfd_offset)) + error (_("Failed to write 'type' to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + switch (record_list->type) + { + case record_reg: /* reg */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing register %d (1 plus 8 plus %d bytes)\n"), + record_list->u.reg.num, + MAX_REGISTER_SIZE); + + /* Write regnum. */ + tmpu64 = record_list->u.reg.num; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + if (!bfdcore_write (obfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to write regnum to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + /* Write regval. */ + if (!bfdcore_write (obfd, osec, record_list->u.reg.val, + MAX_REGISTER_SIZE, &bfd_offset)) + error (_("Failed to write regval to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + break; + + case record_mem: /* mem */ + if (!record_list->u.mem.mem_entry_not_accessible) + { + if (record_debug) + fprintf_unfiltered (gdb_stdlog, _("\ +Writing memory %s (1 plus 8 plus 8 bytes plus %d bytes)\n"), + paddress (gdbarch, + record_list->u.mem.addr), + record_list->u.mem.len); + + /* Write memlen. */ + tmpu64 = record_list->u.mem.len; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + if (!bfdcore_write (obfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to write memlen to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + /* Write memaddr. */ + tmpu64 = record_list->u.mem.addr; + if (BYTE_ORDER == LITTLE_ENDIAN) + tmpu64 = bswap_64 (tmpu64); + if (!bfdcore_write (obfd, osec, &tmpu64, + sizeof (tmpu64), &bfd_offset)) + error (_("Failed to write memaddr to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + + /* Write memval. */ + if (!bfdcore_write (obfd, osec, record_list->u.mem.val, + record_list->u.mem.len, &bfd_offset)) + error (_("Failed to write memval to %s (%s)"), + recfilename, bfd_errmsg (bfd_get_error ())); + } + break; + + case record_end: + /* FIXME: record the contents of record_end rec. */ + if (record_debug) + fprintf_unfiltered (gdb_stdlog, + _("Writing record_end (1 byte)\n")); + break; + } + } + + /* Execute entry. */ + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->next) + record_list = record_list->next; + else + break; + } + + /* Reverse execute to cur_record_list. */ + while (1) + { + /* Check for beginning and end of log. */ + if (record_list == cur_record_list) + break; + + record_exec_entry (regcache, gdbarch, record_list); + + if (record_list->prev) + record_list = record_list->prev; + } + + do_cleanups (set_cleanups); + do_cleanups (old_cleanups); + + /* Succeeded. */ + fprintf_filtered (gdb_stdout, _("Saved recfile %s.\n"), recfilename); +} + /* Truncate the record log from the present point of replay until the end. */ @@ -1185,7 +1958,12 @@ cmd_record_stop (char *args, int from_tt { if (!record_list || !from_tty || query (_("Delete recorded log and " "stop recording?"))) - unpush_target (&record_ops); + { + if (strcmp (current_target.to_shortname, "record") == 0) + unpush_target (&record_ops); + else + unpush_target (&record_core_ops); + } } else printf_unfiltered (_("Process record is not started.\n")); @@ -1203,7 +1981,7 @@ set_record_insn_max_num (char *args, int "the first ones?\n")); while (record_insn_num > record_insn_max_num) - record_list_release_first (); + record_list_release_first_insn (); } } @@ -1243,6 +2021,8 @@ info_record_command (char *args, int fro void _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1250,6 +2030,8 @@ _initialize_record (void) init_record_ops (); add_target (&record_ops); + init_record_core_ops (); + add_target (&record_core_ops); add_setshow_zinteger_cmd ("record", no_class, &record_debug, _("Set debugging of record/replay feature."), @@ -1259,9 +2041,10 @@ _initialize_record (void) NULL, show_record_debug, &setdebuglist, &showdebuglist); - add_prefix_cmd ("record", class_obscure, cmd_record_start, - _("Abbreviated form of \"target record\" command."), - &record_cmdlist, "record ", 0, &cmdlist); + c = add_prefix_cmd ("record", class_obscure, cmd_record_start, + _("Abbreviated form of \"target record\" command."), + &record_cmdlist, "record ", 0, &cmdlist); + set_cmd_completer (c, filename_completer); add_com_alias ("rec", "record", class_obscure, 1); add_prefix_cmd ("record", class_support, set_record_command, _("Set record options"), &set_record_cmdlist, @@ -1276,6 +2059,11 @@ _initialize_record (void) "info record ", 0, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); + c = add_cmd ("dump", class_obscure, cmd_record_dump, + _("Dump the execution records to a file.\n\ +Argument is optional filename. Default filename is 'gdb_record.<process_id>'."), + &record_cmdlist); + set_cmd_completer (c, filename_completer); add_cmd ("delete", class_obscure, cmd_record_delete, _("Delete the rest of execution log and start recording it anew."), [-- Attachment #3: prec-dump-doc.txt --] [-- Type: text/plain, Size: 794 bytes --] --- doc/gdb.texinfo | 10 ++++++++++ 1 file changed, 10 insertions(+) --- a/doc/gdb.texinfo +++ b/doc/gdb.texinfo @@ -5214,6 +5214,16 @@ When record target runs in replay mode ( subsequent execution log and begin to record a new execution log starting from the current address. This means you will abandon the previously recorded ``future'' and begin recording a new ``future''. + +@kindex record dump +@kindex rec dump +@item record dump [@var{file}] +@itemx rec dump [@var{file}] +Dump the execution records of the inferior process to a core file. +The optional argument @var{file} specifies the file name where to put +the record dump. If not specified, the file name defaults +to @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the +inferior process. @end table ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-29 15:53 ` Hui Zhu @ 2009-08-29 18:06 ` Eli Zaretskii 2009-08-29 18:28 ` Hui Zhu 2009-08-29 20:05 ` Michael Snyder 0 siblings, 2 replies; 51+ messages in thread From: Eli Zaretskii @ 2009-08-29 18:06 UTC (permalink / raw) To: Hui Zhu; +Cc: msnyder, gdb-patches > From: Hui Zhu <teawater@gmail.com> > Date: Sat, 29 Aug 2009 23:18:29 +0800 > Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> > > +@kindex record dump > +@kindex rec dump > +@item record dump [@var{file}] > +@itemx rec dump [@var{file}] > +Dump the execution records of the inferior process to a core file. "A core file"? you didn't really mean that, did you? Okay, with that bit fixed. Thanks. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-29 18:06 ` Eli Zaretskii @ 2009-08-29 18:28 ` Hui Zhu 2009-08-29 20:26 ` Eli Zaretskii 2009-08-29 20:05 ` Michael Snyder 1 sibling, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-29 18:28 UTC (permalink / raw) To: Eli Zaretskii; +Cc: msnyder, gdb-patches On Sun, Aug 30, 2009 at 02:01, Eli Zaretskii<eliz@gnu.org> wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Sat, 29 Aug 2009 23:18:29 +0800 >> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> >> >> +@kindex record dump >> +@kindex rec dump >> +@item record dump [@var{file}] >> +@itemx rec dump [@var{file}] >> +Dump the execution records of the inferior process to a core file. > > "A core file"? you didn't really mean that, did you? > > Okay, with that bit fixed. > > Thanks. > Yes, it really dump record log to a core file. Now, record log is together with core file. Thanks, Hui ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-29 18:28 ` Hui Zhu @ 2009-08-29 20:26 ` Eli Zaretskii 2009-08-29 20:39 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Eli Zaretskii @ 2009-08-29 20:26 UTC (permalink / raw) To: Hui Zhu; +Cc: msnyder, gdb-patches > From: Hui Zhu <teawater@gmail.com> > Date: Sun, 30 Aug 2009 02:05:37 +0800 > Cc: msnyder@vmware.com, gdb-patches@sourceware.org > > > "A core file"? you didn't really mean that, did you? > > Yes, it really dump record log to a core file. Now, record log is > together with core file. Sorry, I don't understand: why would we want to have the record log in a core file? What am I missing here? ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-29 20:26 ` Eli Zaretskii @ 2009-08-29 20:39 ` Michael Snyder 2009-08-30 3:03 ` Eli Zaretskii 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-29 20:39 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Hui Zhu, gdb-patches Eli Zaretskii wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Sun, 30 Aug 2009 02:05:37 +0800 >> Cc: msnyder@vmware.com, gdb-patches@sourceware.org >> >>> "A core file"? you didn't really mean that, did you? >> Yes, it really dump record log to a core file. Now, record log is >> together with core file. > > Sorry, I don't understand: why would we want to have the record log in > a core file? What am I missing here? Because the record-log itself does not record starting state -- only changes of state. It is useless by itself if you don't restore the starting state first. Hui's original patch used the "gcore" command to save the starting state, and "target core" to restore it. The problem that I found with that was that it created two independent files, core file and record-log file, and that there was no way to assure ourselves (or the user) that the two files corresponded to one another when they were re-loaded. That's why I suggested combining them into one file -- in this implementation, by adding the record-log to an extra section in the core file. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-29 20:39 ` Michael Snyder @ 2009-08-30 3:03 ` Eli Zaretskii 2009-08-30 5:36 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Eli Zaretskii @ 2009-08-30 3:03 UTC (permalink / raw) To: Michael Snyder; +Cc: teawater, gdb-patches > Date: Sat, 29 Aug 2009 13:33:34 -0700 > From: Michael Snyder <msnyder@vmware.com> > CC: Hui Zhu <teawater@gmail.com>, > "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> > > > Sorry, I don't understand: why would we want to have the record log in > > a core file? What am I missing here? > > Because the record-log itself does not record starting state -- > only changes of state. It is useless by itself if you don't > restore the starting state first. > > Hui's original patch used the "gcore" command to save the > starting state, and "target core" to restore it. > > The problem that I found with that was that it created two > independent files, core file and record-log file, and that > there was no way to assure ourselves (or the user) that the > two files corresponded to one another when they were re-loaded. > > That's why I suggested combining them into one file -- > in this implementation, by adding the record-log to an > extra section in the core file. Thanks for explaining. This all needs to be said in the manual (in a form suitable for the manual, omitting the technicalities and describing this from user perspective). We cannot just say "dump record log to core file". So I hereby revoke my approval of the patch for the manual. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-30 3:03 ` Eli Zaretskii @ 2009-08-30 5:36 ` Hui Zhu 2009-08-30 23:40 ` Eli Zaretskii 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-30 5:36 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Michael Snyder, gdb-patches On Sun, Aug 30, 2009 at 10:56, Eli Zaretskii<eliz@gnu.org> wrote: >> Date: Sat, 29 Aug 2009 13:33:34 -0700 >> From: Michael Snyder <msnyder@vmware.com> >> CC: Hui Zhu <teawater@gmail.com>, >> "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> >> >> > Sorry, I don't understand: why would we want to have the record log in >> > a core file? What am I missing here? >> >> Because the record-log itself does not record starting state -- >> only changes of state. It is useless by itself if you don't >> restore the starting state first. >> >> Hui's original patch used the "gcore" command to save the >> starting state, and "target core" to restore it. >> >> The problem that I found with that was that it created two >> independent files, core file and record-log file, and that >> there was no way to assure ourselves (or the user) that the >> two files corresponded to one another when they were re-loaded. >> >> That's why I suggested combining them into one file -- >> in this implementation, by adding the record-log to an >> extra section in the core file. > > Thanks for explaining. > > This all needs to be said in the manual (in a form suitable for the > manual, omitting the technicalities and describing this from user > perspective). We cannot just say "dump record log to core file". So > I hereby revoke my approval of the patch for the manual. > Agree with you, Eli. Do you have more better words on it? You know my poor english. :) Thanks, Hui ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-30 5:36 ` Hui Zhu @ 2009-08-30 23:40 ` Eli Zaretskii 2009-08-31 7:10 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Eli Zaretskii @ 2009-08-30 23:40 UTC (permalink / raw) To: Hui Zhu; +Cc: msnyder, gdb-patches > From: Hui Zhu <teawater@gmail.com> > Date: Sun, 30 Aug 2009 11:20:32 +0800 > Cc: Michael Snyder <msnyder@vmware.com>, gdb-patches@sourceware.org > > > This all needs to be said in the manual (in a form suitable for the > > manual, omitting the technicalities and describing this from user > > perspective). Â We cannot just say "dump record log to core file". Â So > > I hereby revoke my approval of the patch for the manual. > > > > Agree with you, Eli. Do you have more better words on it? You know > my poor english. :) Something like this: @kindex record dump @kindex rec dump @item record dump [@var{file}] @itemx rec dump [@var{file}] Dump the execution records of the inferior process to the named @var{file}. If not specified, @var{file} defaults to @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the inferior process. The file created by this command is actually a kind of core file, with an extra section that holds the recorded execution log. The sections usually present in a core file capture the state of the inferior before the recording started, so that the file produced by this command can be used to replay the entire recorded session without the need to restore the initial state by some other means. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-30 23:40 ` Eli Zaretskii @ 2009-08-31 7:10 ` Hui Zhu 2009-09-05 3:30 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Hui Zhu @ 2009-08-31 7:10 UTC (permalink / raw) To: Eli Zaretskii; +Cc: msnyder, gdb-patches On Mon, Aug 31, 2009 at 01:58, Eli Zaretskii<eliz@gnu.org> wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Sun, 30 Aug 2009 11:20:32 +0800 >> Cc: Michael Snyder <msnyder@vmware.com>, gdb-patches@sourceware.org >> >> > This all needs to be said in the manual (in a form suitable for the >> > manual, omitting the technicalities and describing this from user >> > perspective). We cannot just say "dump record log to core file". So >> > I hereby revoke my approval of the patch for the manual. >> > >> >> Agree with you, Eli. Do you have more better words on it? You know >> my poor english. :) > > Something like this: > > @kindex record dump > @kindex rec dump > @item record dump [@var{file}] > @itemx rec dump [@var{file}] > Dump the execution records of the inferior process to the named > @var{file}. If not specified, @var{file} defaults to > @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the > inferior process. > > The file created by this command is actually a kind of core file, > with an extra section that holds the recorded execution log. The > sections usually present in a core file capture the state of the > inferior before the recording started, so that the file produced by > this command can be used to replay the entire recorded session > without the need to restore the initial state by some other means. > > Great. Thanks a lot. I make a new doc patch according to it. Hui 2009-08-31 Hui Zhu <teawater@gmail.com> * gdb.texinfo (Process Record and Replay): Document the "record dump" commands. --- doc/gdb.texinfo | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) --- a/doc/gdb.texinfo +++ b/doc/gdb.texinfo @@ -5214,6 +5214,22 @@ When record target runs in replay mode ( subsequent execution log and begin to record a new execution log starting from the current address. This means you will abandon the previously recorded ``future'' and begin recording a new ``future''. + +@kindex record dump +@kindex rec dump +@item record dump [@var{file}] +@itemx rec dump [@var{file}] +Dump the execution records of the inferior process to the named +@var{file}. If not specified, @var{file} defaults to +@file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the +inferior process. + +The file created by this command is actually a kind of core file, +with an extra section that holds the recorded execution log. The +sections usually present in a core file capture the state of the +inferior before the recording started, so that the file produced by +this command can be used to replay the entire recorded session +without the need to restore the initial state by some other means. @end table ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-31 7:10 ` Hui Zhu @ 2009-09-05 3:30 ` Michael Snyder 2009-09-06 16:29 ` Hui Zhu 0 siblings, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-09-05 3:30 UTC (permalink / raw) To: Hui Zhu; +Cc: Eli Zaretskii, gdb-patches Hui Zhu wrote: > On Mon, Aug 31, 2009 at 01:58, Eli Zaretskii<eliz@gnu.org> wrote: >>> From: Hui Zhu <teawater@gmail.com> >>> Date: Sun, 30 Aug 2009 11:20:32 +0800 >>> Cc: Michael Snyder <msnyder@vmware.com>, gdb-patches@sourceware.org >>> >>>> This all needs to be said in the manual (in a form suitable for the >>>> manual, omitting the technicalities and describing this from user >>>> perspective). We cannot just say "dump record log to core file". So >>>> I hereby revoke my approval of the patch for the manual. >>>> >>> Agree with you, Eli. Do you have more better words on it? You know >>> my poor english. :) >> Something like this: >> >> @kindex record dump >> @kindex rec dump >> @item record dump [@var{file}] >> @itemx rec dump [@var{file}] >> Dump the execution records of the inferior process to the named >> @var{file}. If not specified, @var{file} defaults to >> @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the >> inferior process. >> >> The file created by this command is actually a kind of core file, >> with an extra section that holds the recorded execution log. The >> sections usually present in a core file capture the state of the >> inferior before the recording started, so that the file produced by >> this command can be used to replay the entire recorded session >> without the need to restore the initial state by some other means. >> >> > > Great. Thanks a lot. > > I make a new doc patch according to it. > > Hui > > 2009-08-31 Hui Zhu <teawater@gmail.com> > > * gdb.texinfo (Process Record and Replay): Document the > "record dump" commands. > > --- > doc/gdb.texinfo | 16 ++++++++++++++++ > 1 file changed, 16 insertions(+) > > --- a/doc/gdb.texinfo > +++ b/doc/gdb.texinfo > @@ -5214,6 +5214,22 @@ When record target runs in replay mode ( > subsequent execution log and begin to record a new execution log starting > from the current address. This means you will abandon the previously > recorded ``future'' and begin recording a new ``future''. > + > +@kindex record dump > +@kindex rec dump > +@item record dump [@var{file}] > +@itemx rec dump [@var{file}] > +Dump the execution records of the inferior process to the named > +@var{file}. If not specified, @var{file} defaults to > +@file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the > +inferior process. > + > +The file created by this command is actually a kind of core file, > +with an extra section that holds the recorded execution log. The > +sections usually present in a core file capture the state of the > +inferior before the recording started, so that the file produced by > +this command can be used to replay the entire recorded session > +without the need to restore the initial state by some other means. Since there is no "record load" command in this version, perhaps we should say something here about how to reload the file? Something like: To reload the execution record file, first open it like an ordinary core file, then use "target record". Alternatively, maybe you want to add a new version of "record load <filename>" which will do the necessary, by first invoking "core <filename>", and then "target record". > @end table > ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-09-05 3:30 ` Michael Snyder @ 2009-09-06 16:29 ` Hui Zhu 0 siblings, 0 replies; 51+ messages in thread From: Hui Zhu @ 2009-09-06 16:29 UTC (permalink / raw) To: Michael Snyder; +Cc: Eli Zaretskii, gdb-patches On Sat, Sep 5, 2009 at 11:29, Michael Snyder<msnyder@vmware.com> wrote: > Hui Zhu wrote: >> >> On Mon, Aug 31, 2009 at 01:58, Eli Zaretskii<eliz@gnu.org> wrote: >>>> >>>> From: Hui Zhu <teawater@gmail.com> >>>> Date: Sun, 30 Aug 2009 11:20:32 +0800 >>>> Cc: Michael Snyder <msnyder@vmware.com>, gdb-patches@sourceware.org >>>> >>>>> This all needs to be said in the manual (in a form suitable for the >>>>> manual, omitting the technicalities and describing this from user >>>>> perspective). We cannot just say "dump record log to core file". So >>>>> I hereby revoke my approval of the patch for the manual. >>>>> >>>> Agree with you, Eli. Do you have more better words on it? You know >>>> my poor english. :) >>> >>> Something like this: >>> >>> @kindex record dump >>> @kindex rec dump >>> @item record dump [@var{file}] >>> @itemx rec dump [@var{file}] >>> Dump the execution records of the inferior process to the named >>> @var{file}. If not specified, @var{file} defaults to >>> @file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the >>> inferior process. >>> >>> The file created by this command is actually a kind of core file, >>> with an extra section that holds the recorded execution log. The >>> sections usually present in a core file capture the state of the >>> inferior before the recording started, so that the file produced by >>> this command can be used to replay the entire recorded session >>> without the need to restore the initial state by some other means. >>> >>> >> >> Great. Thanks a lot. >> >> I make a new doc patch according to it. >> >> Hui >> >> 2009-08-31 Hui Zhu <teawater@gmail.com> >> >> * gdb.texinfo (Process Record and Replay): Document the >> "record dump" commands. >> >> --- >> doc/gdb.texinfo | 16 ++++++++++++++++ >> 1 file changed, 16 insertions(+) >> >> --- a/doc/gdb.texinfo >> +++ b/doc/gdb.texinfo >> @@ -5214,6 +5214,22 @@ When record target runs in replay mode ( >> subsequent execution log and begin to record a new execution log starting >> from the current address. This means you will abandon the previously >> recorded ``future'' and begin recording a new ``future''. >> + >> +@kindex record dump >> +@kindex rec dump >> +@item record dump [@var{file}] >> +@itemx rec dump [@var{file}] >> +Dump the execution records of the inferior process to the named >> +@var{file}. If not specified, @var{file} defaults to >> +@file{gdb_record.@var{pid}}, where @var{pid} is is the PID of the >> +inferior process. >> + >> +The file created by this command is actually a kind of core file, >> +with an extra section that holds the recorded execution log. The >> +sections usually present in a core file capture the state of the >> +inferior before the recording started, so that the file produced by >> +this command can be used to replay the entire recorded session >> +without the need to restore the initial state by some other means. > > Since there is no "record load" command in this version, perhaps > we should say something here about how to reload the file? > > Something like: > > To reload the execution record file, first open it like an > ordinary core file, then use "target record". > > Alternatively, maybe you want to add a new version of > "record load <filename>" which will do the necessary, > by first invoking "core <filename>", and then "target > record". > > >> @end table >> > > I am not sure which way is better. Could you help me with it? Thanks, Hui ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-29 18:06 ` Eli Zaretskii 2009-08-29 18:28 ` Hui Zhu @ 2009-08-29 20:05 ` Michael Snyder 2009-08-29 20:33 ` Eli Zaretskii 1 sibling, 1 reply; 51+ messages in thread From: Michael Snyder @ 2009-08-29 20:05 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Hui Zhu, gdb-patches Eli Zaretskii wrote: >> From: Hui Zhu <teawater@gmail.com> >> Date: Sat, 29 Aug 2009 23:18:29 +0800 >> Cc: Eli Zaretskii <eliz@gnu.org>, "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> >> >> +@kindex record dump >> +@kindex rec dump >> +@item record dump [@var{file}] >> +@itemx rec dump [@var{file}] >> +Dump the execution records of the inferior process to a core file. > > "A core file"? you didn't really mean that, did you? Yep! That was my suggestion. We use a core file to capture the state at the beginning of the recording, then add an extra bfd section to the corefile and write the recording log into that. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-29 20:05 ` Michael Snyder @ 2009-08-29 20:33 ` Eli Zaretskii 2009-08-29 21:20 ` Michael Snyder 0 siblings, 1 reply; 51+ messages in thread From: Eli Zaretskii @ 2009-08-29 20:33 UTC (permalink / raw) To: Michael Snyder; +Cc: teawater, gdb-patches > Date: Sat, 29 Aug 2009 11:29:04 -0700 > From: Michael Snyder <msnyder@vmware.com> > CC: Hui Zhu <teawater@gmail.com>, > "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> > > > "A core file"? you didn't really mean that, did you? > > Yep! That was my suggestion. We use a core file to capture the > state at the beginning of the recording, then add an extra bfd > section to the corefile and write the recording log into that. What is this useful for? ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [RFA/RFC] Add dump and load command to process record and replay 2009-08-29 20:33 ` Eli Zaretskii @ 2009-08-29 21:20 ` Michael Snyder 0 siblings, 0 replies; 51+ messages in thread From: Michael Snyder @ 2009-08-29 21:20 UTC (permalink / raw) To: Eli Zaretskii; +Cc: teawater, gdb-patches Eli Zaretskii wrote: >> Date: Sat, 29 Aug 2009 11:29:04 -0700 >> From: Michael Snyder <msnyder@vmware.com> >> CC: Hui Zhu <teawater@gmail.com>, >> "gdb-patches@sourceware.org" <gdb-patches@sourceware.org> >> >>> "A core file"? you didn't really mean that, did you? >> Yep! That was my suggestion. We use a core file to capture the >> state at the beginning of the recording, then add an extra bfd >> section to the corefile and write the recording log into that. > > What is this useful for? I'm replying twice, just in case one of my explanations happens to 'click' better than the other. The record-log that process-record keeps in memory records only changes in state -- it does not contain a 'snapshot' of the entire state at any point (eg. at the beginning of the session). Therefore you can't use it unles you can first duplicate the starting state. Hui's approach was to first save a corefile, to record the starting state, and then save the changes file separately. A brilliant idea, but it has a drawback -- since they are separate files, there is no way to protect users against loading a start state from one program and a change set from a completely different program. ^ permalink raw reply [flat|nested] 51+ messages in thread
end of thread, other threads:[~2022-04-13 12:21 UTC | newest] Thread overview: 51+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2022-01-21 6:46 [RFA/RFC] Add dump and load command to process record and replay Simon Sobisch via Gdb-patches 2022-01-24 9:26 ` Hui Zhu via Gdb-patches 2022-04-13 12:21 ` Simon Sobisch via Gdb-patches -- strict thread matches above, loose matches on Subject: below -- 2009-08-01 7:31 Hui Zhu 2009-08-01 9:57 ` Eli Zaretskii 2009-08-01 19:20 ` Michael Snyder 2009-08-02 3:18 ` Michael Snyder 2009-08-02 5:58 ` Hui Zhu 2009-08-03 4:12 ` Hui Zhu 2009-08-03 18:29 ` Eli Zaretskii 2009-08-04 1:58 ` Hui Zhu 2009-08-04 2:07 ` Hui Zhu 2009-08-04 18:26 ` Eli Zaretskii 2009-08-04 20:01 ` Michael Snyder 2009-08-05 9:21 ` Hui Zhu 2009-08-05 21:23 ` Michael Snyder 2009-08-06 3:14 ` Eli Zaretskii 2009-08-06 14:16 ` Hui Zhu 2009-08-07 3:27 ` Michael Snyder 2009-08-07 3:29 ` Hui Zhu 2009-08-07 3:34 ` Michael Snyder 2009-08-07 4:06 ` Hui Zhu 2009-08-07 8:41 ` Eli Zaretskii 2009-08-07 9:53 ` Hui Zhu 2009-08-07 12:51 ` Eli Zaretskii 2009-08-07 16:22 ` Hui Zhu 2009-08-07 17:42 ` Michael Snyder 2009-08-08 13:28 ` Hui Zhu 2009-08-10 3:09 ` Michael Snyder 2009-08-22 17:39 ` Hui Zhu 2009-08-23 1:14 ` Hui Zhu 2009-08-23 23:43 ` Michael Snyder 2009-08-24 8:20 ` Hui Zhu 2009-08-24 18:32 ` Michael Snyder 2009-08-25 8:47 ` Hui Zhu 2009-08-26 1:40 ` Michael Snyder 2009-08-26 2:59 ` Michael Snyder 2009-08-29 15:53 ` Hui Zhu 2009-08-29 18:06 ` Eli Zaretskii 2009-08-29 18:28 ` Hui Zhu 2009-08-29 20:26 ` Eli Zaretskii 2009-08-29 20:39 ` Michael Snyder 2009-08-30 3:03 ` Eli Zaretskii 2009-08-30 5:36 ` Hui Zhu 2009-08-30 23:40 ` Eli Zaretskii 2009-08-31 7:10 ` Hui Zhu 2009-09-05 3:30 ` Michael Snyder 2009-09-06 16:29 ` Hui Zhu 2009-08-29 20:05 ` Michael Snyder 2009-08-29 20:33 ` Eli Zaretskii 2009-08-29 21:20 ` Michael Snyder
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox