From: Hui Zhu <teawater@gmail.com>
To: Michael Snyder <msnyder@vmware.com>
Cc: Eli Zaretskii <eliz@gnu.org>,
"gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
Subject: Re: [RFA/RFC] Add dump and load command to process record and replay
Date: Sat, 29 Aug 2009 15:53:00 -0000 [thread overview]
Message-ID: <daef60380908290818i28337ecct234aa2e834a9009c@mail.gmail.com> (raw)
In-Reply-To: <4A949176.9060604@vmware.com>
[-- 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
next prev parent reply other threads:[~2009-08-29 15:19 UTC|newest]
Thread overview: 57+ messages / expand[flat|nested] mbox.gz Atom feed top
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 20:19 ` [RFA/RFC] Add dump and load command to process record (file format etc) Michael Snyder
2009-08-06 2:17 ` Hui Zhu
2009-08-12 14:11 ` Michael Snyder
2009-08-12 15:16 ` Tom Tromey
2009-08-12 22:38 ` Michael Snyder
2009-08-16 0:04 ` Hui Zhu
2009-08-05 21:23 ` [RFA/RFC] Add dump and load command to process record and replay 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 [this message]
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
2022-01-21 6:46 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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=daef60380908290818i28337ecct234aa2e834a9009c@mail.gmail.com \
--to=teawater@gmail.com \
--cc=eliz@gnu.org \
--cc=gdb-patches@sourceware.org \
--cc=msnyder@vmware.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox