2009-10-16 Hui Zhu <teawater@gmail.com>

Michael Snyder  <msnyder@msnyder-server.eng.vmware.com>

	* record.c (RECORD_FILE_MAGIC): New constant.
	(record_arch_list_cleanups): Renamed from record_message_cleanups.
	(bfdcore_read): New function.
	(netorder64): New function.
	(netorder32): New function.
	(netorder16): New function.
	(record_restore): New function.  Restore a saved record log.
	(bfdcore_write): New function.
	(cmd_record_restore): New function.
	(cmd_record_save): New function.  Save a record log to a file.
	(_initialize_record): Set up commands for save and restore.

2009-10-17  Michael Snyder  <msnyder@vmware.com>

	* gcore.h: New file.
	* gcore.c (create_gcore_bfd): New function.
	(write_gcore_file): New function.
	(call_target_sbrk): New function.  Abstracted from gcore_command.
	(gcore_command): Call call_target_sbrk.
This commit is contained in:
Michael Snyder
2009-10-22 19:36:06 +00:00
parent 27699eeaab
commit 0156b21832
4 changed files with 711 additions and 82 deletions

View File

@ -1,3 +1,26 @@
2009-10-16 Hui Zhu <teawater@gmail.com>
Michael Snyder <msnyder@msnyder-server.eng.vmware.com>
* record.c (RECORD_FILE_MAGIC): New constant.
(record_arch_list_cleanups): Renamed from record_message_cleanups.
(bfdcore_read): New function.
(netorder64): New function.
(netorder32): New function.
(netorder16): New function.
(record_restore): New function. Restore a saved record log.
(bfdcore_write): New function.
(cmd_record_restore): New function.
(cmd_record_save): New function. Save a record log to a file.
(_initialize_record): Set up commands for save and restore.
2009-10-17 Michael Snyder <msnyder@vmware.com>
* gcore.h: New file.
* gcore.c (create_gcore_bfd): New function.
(write_gcore_file): New function.
(call_target_sbrk): New function. Abstracted from gcore_command.
(gcore_command): Call call_target_sbrk.
2009-10-22 Hui Zhu <teawater@gmail.com> 2009-10-22 Hui Zhu <teawater@gmail.com>
Michael Snyder <msnyder@vmware.com> Michael Snyder <msnyder@vmware.com>

View File

@ -25,10 +25,14 @@
#include "gdbcore.h" #include "gdbcore.h"
#include "objfiles.h" #include "objfiles.h"
#include "symfile.h" #include "symfile.h"
#include "arch-utils.h"
#include "completer.h"
#include "gcore.h"
#include "cli/cli-decode.h" #include "cli/cli-decode.h"
#include "gdb_assert.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 /* 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 must throttle it to limit the amount of memory used by GDB during
@ -40,45 +44,30 @@ static enum bfd_architecture default_gcore_arch (void);
static unsigned long default_gcore_mach (void); static unsigned long default_gcore_mach (void);
static int gcore_memory_sections (bfd *); static int gcore_memory_sections (bfd *);
/* Generate a core file from the inferior process. */ /* create_gcore_bfd -- helper for gcore_command (exported).
Open a new bfd core file for output, and return the handle. */
static void bfd *
gcore_command (char *args, int from_tty) create_gcore_bfd (char *filename)
{ {
struct cleanup *old_chain; bfd *obfd = bfd_openw (filename, default_gcore_target ());
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 ());
if (!obfd) if (!obfd)
error (_("Failed to open '%s' for output."), corefilename); error (_("Failed to open '%s' for output."), filename);
/* Need a cleanup that will close the file (FIXME: delete it?). */
old_chain = make_cleanup_bfd_close (obfd);
bfd_set_format (obfd, bfd_core); bfd_set_format (obfd, bfd_core);
bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ()); bfd_set_arch_mach (obfd, default_gcore_arch (), default_gcore_mach ());
return obfd;
}
/* write_gcore_file -- helper for gcore_command (exported).
Compose and write the corefile data to the core file. */
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. */ /* An external target method must build the notes section. */
note_data = target_make_corefile_notes (obfd, &note_size); note_data = target_make_corefile_notes (obfd, &note_size);
@ -107,8 +96,46 @@ gcore_command (char *args, int from_tty)
if (note_data != NULL && note_size != 0) if (note_data != NULL && note_size != 0)
{ {
if (!bfd_set_section_contents (obfd, note_sec, note_data, 0, note_size)) 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. */ /* Succeeded. */
fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename); fprintf_filtered (gdb_stdout, "Saved corefile %s\n", corefilename);
@ -212,6 +239,50 @@ derive_stack_segment (bfd_vma *bottom, bfd_vma *top)
return 1; return 1;
} }
/* call_target_sbrk --
helper function for derive_heap_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 /* Derive a reasonable heap segment for ABFD by looking at sbrk and
the static data sections. Store its limits in *BOTTOM and *TOP. the static data sections. Store its limits in *BOTTOM and *TOP.
Return non-zero if successful. */ Return non-zero if successful. */
@ -219,12 +290,10 @@ derive_stack_segment (bfd_vma *bottom, bfd_vma *top)
static int static int
derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top) derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top)
{ {
struct objfile *sbrk_objf;
struct gdbarch *gdbarch; struct gdbarch *gdbarch;
bfd_vma top_of_data_memory = 0; bfd_vma top_of_data_memory = 0;
bfd_vma top_of_heap = 0; bfd_vma top_of_heap = 0;
bfd_size_type sec_size; bfd_size_type sec_size;
struct value *zero, *sbrk;
bfd_vma sec_vaddr; bfd_vma sec_vaddr;
asection *sec; asection *sec;
@ -259,30 +328,10 @@ derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top)
} }
} }
/* Now get the top-of-heap by calling sbrk in the inferior. */ top_of_heap = call_target_sbrk (0);
if (lookup_minimal_symbol ("sbrk", NULL, NULL) != NULL) if (top_of_heap == (bfd_vma) 0)
{
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; 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)
return 0;
top_of_heap = value_as_long (sbrk);
/* Return results. */ /* Return results. */
if (top_of_heap > top_of_data_memory) if (top_of_heap > top_of_data_memory)
{ {
@ -299,13 +348,15 @@ static void
make_output_phdrs (bfd *obfd, asection *osec, void *ignored) make_output_phdrs (bfd *obfd, asection *osec, void *ignored)
{ {
int p_flags = 0; int p_flags = 0;
int p_type; int p_type = 0;
/* FIXME: these constants may only be applicable for ELF. */ /* FIXME: these constants may only be applicable for ELF. */
if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0) if (strncmp (bfd_section_name (obfd, osec), "load", 4) == 0)
p_type = PT_LOAD; p_type = PT_LOAD;
else else if (strncmp (bfd_section_name (obfd, osec), "note", 4) == 0)
p_type = PT_NOTE; p_type = PT_NOTE;
else
p_type = PT_NULL;
p_flags |= PF_R; /* Segment is readable. */ p_flags |= PF_R; /* Segment is readable. */
if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY)) if (!(bfd_get_section_flags (obfd, osec) & SEC_READONLY))

27
gdb/gcore.h Normal file
View File

@ -0,0 +1,27 @@
/* 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/>. */
#if !defined (GCORE_H)
#define GCORE_H 1
extern bfd *create_gcore_bfd (char *filename);
extern void write_gcore_file (bfd *obfd);
extern bfd *load_corefile (char *filename, int from_tty);
#endif /* GCORE_H */

View File

@ -23,10 +23,15 @@
#include "gdbthread.h" #include "gdbthread.h"
#include "event-top.h" #include "event-top.h"
#include "exceptions.h" #include "exceptions.h"
#include "completer.h"
#include "arch-utils.h"
#include "gdbcore.h" #include "gdbcore.h"
#include "exec.h" #include "exec.h"
#include "record.h" #include "record.h"
#include "elf-bfd.h"
#include "gcore.h"
#include <byteswap.h>
#include <signal.h> #include <signal.h>
/* This module implements "target record", also known as "process /* This module implements "target record", also known as "process
@ -54,6 +59,8 @@
#define RECORD_IS_REPLAY \ #define RECORD_IS_REPLAY \
(record_list->next || execution_direction == EXEC_REVERSE) (record_list->next || execution_direction == EXEC_REVERSE)
#define RECORD_FILE_MAGIC netorder32(0x20091016)
/* These are the core structs of the process record functionality. /* These are the core structs of the process record functionality.
A record_entry is a record of the value change of a register A record_entry is a record of the value change of a register
@ -545,24 +552,24 @@ record_check_insn_num (int set_terminal)
if (q) if (q)
record_stop_at_limit = 0; record_stop_at_limit = 0;
else else
error (_("Process record: inferior program stopped.")); error (_("Process record: stopped 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 /* Before inferior step (when GDB record the running message, inferior
only can step), GDB will call this function to record the values to only can step), GDB will call this function to record the values to
record_list. This function will call gdbarch_process_record to record_list. This function will call gdbarch_process_record to
record the running message of inferior and set them to record the running message of inferior and set them to
record_arch_list, and add it to record_list. */ record_arch_list, and add it to record_list. */
static void
record_message_cleanups (void *ignore)
{
record_list_release (record_arch_list_tail);
}
struct record_message_args { struct record_message_args {
struct regcache *regcache; struct regcache *regcache;
enum target_signal signal; enum target_signal signal;
@ -574,7 +581,7 @@ record_message (void *args)
int ret; int ret;
struct record_message_args *myargs = args; struct record_message_args *myargs = args;
struct gdbarch *gdbarch = get_regcache_arch (myargs->regcache); struct gdbarch *gdbarch = get_regcache_arch (myargs->regcache);
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_head = NULL;
record_arch_list_tail = NULL; record_arch_list_tail = NULL;
@ -714,8 +721,8 @@ record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
{ {
entry->u.mem.mem_entry_not_accessible = 1; entry->u.mem.mem_entry_not_accessible = 1;
if (record_debug) if (record_debug)
warning (_("Process record: error reading memory at " warning ("Process record: error reading memory at "
"addr = %s len = %d."), "addr = %s len = %d.",
paddress (gdbarch, entry->u.mem.addr), paddress (gdbarch, entry->u.mem.addr),
entry->u.mem.len); entry->u.mem.len);
} }
@ -727,8 +734,8 @@ record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
{ {
entry->u.mem.mem_entry_not_accessible = 1; entry->u.mem.mem_entry_not_accessible = 1;
if (record_debug) if (record_debug)
warning (_("Process record: error writing memory at " warning ("Process record: error writing memory at "
"addr = %s len = %d."), "addr = %s len = %d.",
paddress (gdbarch, entry->u.mem.addr), paddress (gdbarch, entry->u.mem.addr),
entry->u.mem.len); entry->u.mem.len);
} }
@ -765,6 +772,8 @@ static int (*tmp_to_insert_breakpoint) (struct gdbarch *,
static int (*tmp_to_remove_breakpoint) (struct gdbarch *, static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
struct bp_target_info *); struct bp_target_info *);
static void record_restore (void);
/* Open the process record target. */ /* Open the process record target. */
static void static void
@ -791,6 +800,7 @@ record_core_open_1 (char *name, int from_tty)
} }
push_target (&record_core_ops); push_target (&record_core_ops);
record_restore ();
} }
/* "to_open" target method for 'live' processes. */ /* "to_open" target method for 'live' processes. */
@ -1438,8 +1448,8 @@ record_xfer_partial (struct target_ops *ops, enum target_object object,
record_list_release (record_arch_list_tail); record_list_release (record_arch_list_tail);
if (record_debug) if (record_debug)
fprintf_unfiltered (gdb_stdlog, fprintf_unfiltered (gdb_stdlog,
_("Process record: failed to record " "Process record: failed to record "
"execution log.")); "execution log.");
return -1; return -1;
} }
if (record_arch_list_add_end ()) if (record_arch_list_add_end ())
@ -1447,8 +1457,8 @@ record_xfer_partial (struct target_ops *ops, enum target_object object,
record_list_release (record_arch_list_tail); record_list_release (record_arch_list_tail);
if (record_debug) if (record_debug)
fprintf_unfiltered (gdb_stdlog, fprintf_unfiltered (gdb_stdlog,
_("Process record: failed to record " "Process record: failed to record "
"execution log.")); "execution log.");
return -1; return -1;
} }
record_list->next = record_arch_list_head; record_list->next = record_arch_list_head;
@ -1885,9 +1895,513 @@ info_record_command (char *args, int from_tty)
record_insn_max_num); record_insn_max_num);
} }
/* Record log save-file format
Version 1 (never released)
Header:
4 bytes: magic number htonl(0x20090829).
NOTE: be sure to change whenever this file format changes!
Records:
record_end:
1 byte: record type (record_end, see enum record_type).
record_reg:
1 byte: record type (record_reg, see enum record_type).
8 bytes: register id (network byte order).
MAX_REGISTER_SIZE bytes: register value.
record_mem:
1 byte: record type (record_mem, see enum record_type).
8 bytes: memory length (network byte order).
8 bytes: memory address (network byte order).
n bytes: memory value (n == memory length).
Version 2
4 bytes: magic number netorder32(0x20091016).
NOTE: be sure to change whenever this file format changes!
Records:
record_end:
1 byte: record type (record_end, see enum record_type).
4 bytes: signal
4 bytes: instruction count
record_reg:
1 byte: record type (record_reg, see enum record_type).
4 bytes: register id (network byte order).
n bytes: register value (n == actual register size).
(eg. 4 bytes for x86 general registers).
record_mem:
1 byte: record type (record_mem, see enum record_type).
4 bytes: memory length (network byte order).
8 bytes: memory address (network byte order).
n bytes: memory value (n == memory length).
*/
/* bfdcore_read -- read bytes from a core file section. */
static inline void
bfdcore_read (bfd *obfd, asection *osec, void *buf, int len, int *offset)
{
int ret = bfd_get_section_contents (obfd, osec, buf, *offset, len);
if (ret)
*offset += len;
else
error (_("Failed to read %d bytes from core file %s ('%s').\n"),
len, bfd_get_filename (obfd),
bfd_errmsg (bfd_get_error ()));
}
static inline uint64_t
netorder64 (uint64_t fromfile)
{
return (BYTE_ORDER == LITTLE_ENDIAN)
? bswap_64 (fromfile)
: fromfile;
}
static inline uint32_t
netorder32 (uint32_t fromfile)
{
return (BYTE_ORDER == LITTLE_ENDIAN)
? bswap_32 (fromfile)
: fromfile;
}
static inline uint16_t
netorder16 (uint16_t fromfile)
{
return (BYTE_ORDER == LITTLE_ENDIAN)
? bswap_16 (fromfile)
: fromfile;
}
/* Restore the execution log from a core_bfd file. */
static void
record_restore (void)
{
uint32_t magic;
struct cleanup *old_cleanups;
struct record_entry *rec;
asection *osec;
uint32_t osec_size;
int bfd_offset = 0;
struct regcache *regcache;
/* We restore the execution log from the open core bfd,
if there is one. */
if (core_bfd == NULL)
return;
/* "record_restore" can only be called when record list is empty. */
gdb_assert (record_first.next == NULL);
if (record_debug)
printf_filtered ("Restoring recording from core file.\n");
/* Now need to find our special note section. */
osec = bfd_get_section_by_name (core_bfd, "null0");
osec_size = bfd_section_size (core_bfd, osec);
if (record_debug)
printf_filtered ("Find precord section %s.\n",
osec ? "succeeded" : "failed");
if (!osec)
return;
if (record_debug)
printf_filtered ("%s", bfd_section_name (core_bfd, osec));
/* Check the magic code. */
bfdcore_read (core_bfd, osec, &magic, sizeof (magic), &bfd_offset);
if (magic != RECORD_FILE_MAGIC)
error (_("Version mis-match or file format error in core file %s."),
bfd_get_filename (core_bfd));
if (record_debug)
printf_filtered ("\
Reading 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n",
netorder32 (magic));
/* Restore the entries in recfd into record_arch_list_head and
record_arch_list_tail. */
record_arch_list_head = NULL;
record_arch_list_tail = NULL;
record_insn_num = 0;
old_cleanups = make_cleanup (record_arch_list_cleanups, 0);
regcache = get_current_regcache ();
while (1)
{
int ret;
uint8_t tmpu8;
uint32_t regnum, len, signal, count;
uint64_t addr;
/* We are finished when offset reaches osec_size. */
if (bfd_offset >= osec_size)
break;
bfdcore_read (core_bfd, osec, &tmpu8, sizeof (tmpu8), &bfd_offset);
switch (tmpu8)
{
case record_reg: /* reg */
/* Get register number to regnum. */
bfdcore_read (core_bfd, osec, &regnum,
sizeof (regnum), &bfd_offset);
regnum = netorder32 (regnum);
rec = record_reg_alloc (regcache, regnum);
/* Get val. */
bfdcore_read (core_bfd, osec, record_get_loc (rec),
rec->u.reg.len, &bfd_offset);
if (record_debug)
printf_filtered ("\
Reading register %d (1 plus %d plus %d bytes)\n",
rec->u.reg.num,
sizeof (regnum),
rec->u.reg.len);
break;
case record_mem: /* mem */
/* Get len. */
bfdcore_read (core_bfd, osec, &len,
sizeof (len), &bfd_offset);
len = netorder32 (len);
/* Get addr. */
bfdcore_read (core_bfd, osec, &addr,
sizeof (addr), &bfd_offset);
addr = netorder64 (addr);
rec = record_mem_alloc (addr, len);
/* Get val. */
bfdcore_read (core_bfd, osec, record_get_loc (rec),
rec->u.mem.len, &bfd_offset);
if (record_debug)
printf_filtered ("\
Reading memory %s (1 plus %d plus %d plus %d bytes)\n",
paddress (get_current_arch (),
rec->u.mem.addr),
sizeof (addr),
sizeof (len),
rec->u.mem.len);
break;
case record_end: /* end */
rec = record_end_alloc ();
record_insn_num ++;
/* Get signal value. */
bfdcore_read (core_bfd, osec, &signal,
sizeof (signal), &bfd_offset);
signal = netorder32 (signal);
rec->u.end.sigval = signal;
/* Get insn count. */
bfdcore_read (core_bfd, osec, &count,
sizeof (count), &bfd_offset);
count = netorder32 (count);
rec->u.end.insn_num = count;
record_insn_count = count + 1;
if (record_debug)
printf_filtered ("\
Reading record_end (1 + %d + %d bytes), offset == %s\n",
sizeof (signal),
sizeof (count),
paddress (get_current_arch (),
bfd_offset));
break;
default:
error (_("Bad entry type in core file %s."),
bfd_get_filename (core_bfd));
break;
}
/* Add rec to record arch list. */
record_arch_list_add (rec);
}
discard_cleanups (old_cleanups);
/* Add record_arch_list_head to the end of record list. */
record_first.next = record_arch_list_head;
record_arch_list_head->prev = &record_first;
record_arch_list_tail->next = NULL;
record_list = &record_first;
/* Update record_insn_max_num. */
if (record_insn_num > record_insn_max_num)
{
record_insn_max_num = record_insn_num;
warning (_("Auto increase record/replay buffer limit to %d."),
record_insn_max_num);
}
/* Succeeded. */
printf_filtered (_("Restored records from core file %s.\n"),
bfd_get_filename (core_bfd));
print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
}
/* bfdcore_write -- write bytes into a core file section. */
static inline void
bfdcore_write (bfd *obfd, asection *osec, void *buf, int len, int *offset)
{
int ret = bfd_set_section_contents (obfd, osec, buf, *offset, len);
if (ret)
*offset += len;
else
error (_("Failed to write %d bytes to core file %s ('%s').\n"),
len, bfd_get_filename (obfd),
bfd_errmsg (bfd_get_error ()));
}
/* Restore the execution log from a file. We use a modified elf
corefile format, with an extra section for our data. */
static void
cmd_record_restore (char *args, int from_tty)
{
core_file_command (args, from_tty);
record_open (args, from_tty);
}
static void
record_save_cleanups (void *data)
{
bfd *obfd = data;
char *pathname = xstrdup (bfd_get_filename (obfd));
bfd_close (obfd);
unlink (pathname);
xfree (pathname);
}
/* Save the execution log to a file. We use a modified elf corefile
format, with an extra section for our data. */
static void
cmd_record_save (char *args, int from_tty)
{
char *recfilename, recfilename_buffer[40];
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 save_size = 0;
asection *osec = NULL;
int bfd_offset = 0;
if (strcmp (current_target.to_shortname, "record") != 0)
error (_("This command can only be used with target 'record'.\n"
"Use 'target record' first.\n"));
if (args && *args)
recfilename = args;
else
{
/* Default recfile name is "gdb_record.PID". */
snprintf (recfilename_buffer, sizeof (recfilename_buffer),
"gdb_record.%d", PIDGET (inferior_ptid));
recfilename = recfilename_buffer;
}
/* Open the save file. */
if (record_debug)
printf_filtered ("Saving execution log to core file '%s'\n", recfilename);
/* Open the output file. */
obfd = create_gcore_bfd (recfilename);
old_cleanups = make_cleanup (record_save_cleanups, obfd);
/* Save the current record entry to "cur_record_list". */
cur_record_list = record_list;
/* Get the values of regcache and gdbarch. */
regcache = get_current_regcache ();
gdbarch = get_regcache_arch (regcache);
/* Disable the GDB operation record. */
set_cleanups = record_gdb_operation_disable_set ();
/* Reverse execute to the begin of record list. */
while (1)
{
/* Check for beginning and end of log. */
if (record_list == &record_first)
break;
record_exec_insn (regcache, gdbarch, record_list);
if (record_list->prev)
record_list = record_list->prev;
}
/* Compute the size needed for the extra bfd section. */
save_size = 4; /* magic cookie */
for (record_list = record_first.next; record_list;
record_list = record_list->next)
switch (record_list->type)
{
case record_end:
save_size += 1 + 4 + 4;
break;
case record_reg:
save_size += 1 + 4 + record_list->u.reg.len;
break;
case record_mem:
save_size += 1 + 4 + 8 + record_list->u.mem.len;
break;
}
/* Make the new bfd section. */
osec = bfd_make_section_anyway_with_flags (obfd, "precord",
SEC_HAS_CONTENTS
| SEC_READONLY);
if (osec == NULL)
error (_("Failed to create 'precord' section for corefile %s: %s"),
recfilename,
bfd_errmsg (bfd_get_error ()));
bfd_set_section_size (obfd, osec, save_size);
bfd_set_section_vma (obfd, osec, 0);
bfd_set_section_alignment (obfd, osec, 0);
bfd_section_lma (obfd, osec) = 0;
/* Save corefile state. */
write_gcore_file (obfd);
/* Write out the record log. */
/* Write the magic code. */
magic = RECORD_FILE_MAGIC;
if (record_debug)
printf_filtered ("\
Writing 4-byte magic cookie RECORD_FILE_MAGIC (0x%08x)\n",
magic);
bfdcore_write (obfd, osec, &magic, sizeof (magic), &bfd_offset);
/* Save the entries to recfd and forward execute to the end of
record list. */
record_list = &record_first;
while (1)
{
/* Save entry. */
if (record_list != &record_first)
{
uint8_t type;
uint32_t regnum, len, signal, count;
uint64_t addr;
type = record_list->type;
bfdcore_write (obfd, osec, &type, sizeof (type), &bfd_offset);
switch (record_list->type)
{
case record_reg: /* reg */
if (record_debug)
printf_filtered ("\
Writing register %d (1 plus %d plus %d bytes)\n",
record_list->u.reg.num,
sizeof (regnum),
record_list->u.reg.len);
/* Write regnum. */
regnum = netorder32 (record_list->u.reg.num);
bfdcore_write (obfd, osec, &regnum,
sizeof (regnum), &bfd_offset);
/* Write regval. */
bfdcore_write (obfd, osec, record_get_loc (record_list),
record_list->u.reg.len, &bfd_offset);
break;
case record_mem: /* mem */
if (record_debug)
printf_filtered ("\
Writing memory %s (1 plus %d plus %d plus %d bytes)\n",
paddress (gdbarch,
record_list->u.mem.addr),
sizeof (addr),
sizeof (len),
record_list->u.mem.len);
/* Write memlen. */
len = netorder32 (record_list->u.mem.len);
bfdcore_write (obfd, osec, &len, sizeof (len), &bfd_offset);
/* Write memaddr. */
addr = netorder64 (record_list->u.mem.addr);
bfdcore_write (obfd, osec, &addr,
sizeof (addr), &bfd_offset);
/* Write memval. */
bfdcore_write (obfd, osec, record_get_loc (record_list),
record_list->u.mem.len, &bfd_offset);
break;
case record_end:
if (record_debug)
printf_filtered ("\
Writing record_end (1 + %d + %d bytes)\n",
sizeof (signal),
sizeof (count));
/* Write signal value. */
signal = netorder32 (record_list->u.end.sigval);
bfdcore_write (obfd, osec, &signal,
sizeof (signal), &bfd_offset);
/* Write insn count. */
count = netorder32 (record_list->u.end.insn_num);
bfdcore_write (obfd, osec, &count,
sizeof (count), &bfd_offset);
break;
}
}
/* Execute entry. */
record_exec_insn (regcache, gdbarch, record_list);
if (record_list->next)
record_list = record_list->next;
else
break;
}
/* Reverse execute to cur_record_list. */
while (1)
{
/* Check for beginning and end of log. */
if (record_list == cur_record_list)
break;
record_exec_insn (regcache, gdbarch, record_list);
if (record_list->prev)
record_list = record_list->prev;
}
do_cleanups (set_cleanups);
bfd_close (obfd);
discard_cleanups (old_cleanups);
/* Succeeded. */
printf_filtered (_("Saved core file %s with execution log.\n"),
recfilename);
}
void void
_initialize_record (void) _initialize_record (void)
{ {
struct cmd_list_element *c;
/* Init record_first. */ /* Init record_first. */
record_first.prev = NULL; record_first.prev = NULL;
record_first.next = NULL; record_first.next = NULL;
@ -1906,9 +2420,11 @@ _initialize_record (void)
NULL, show_record_debug, &setdebuglist, NULL, show_record_debug, &setdebuglist,
&showdebuglist); &showdebuglist);
add_prefix_cmd ("record", class_obscure, cmd_record_start, c = add_prefix_cmd ("record", class_obscure, cmd_record_start,
_("Abbreviated form of \"target record\" command."), _("Abbreviated form of \"target record\" command."),
&record_cmdlist, "record ", 0, &cmdlist); &record_cmdlist, "record ", 0, &cmdlist);
set_cmd_completer (c, filename_completer);
add_com_alias ("rec", "record", class_obscure, 1); add_com_alias ("rec", "record", class_obscure, 1);
add_prefix_cmd ("record", class_support, set_record_command, add_prefix_cmd ("record", class_support, set_record_command,
_("Set record options"), &set_record_cmdlist, _("Set record options"), &set_record_cmdlist,
@ -1923,6 +2439,18 @@ _initialize_record (void)
"info record ", 0, &infolist); "info record ", 0, &infolist);
add_alias_cmd ("rec", "record", class_obscure, 1, &infolist); add_alias_cmd ("rec", "record", class_obscure, 1, &infolist);
c = add_cmd ("save", class_obscure, cmd_record_save,
_("Save the execution log to a file.\n\
Argument is optional filename.\n\
Default filename is 'gdb_record.<process_id>'."),
&record_cmdlist);
set_cmd_completer (c, filename_completer);
c = add_cmd ("restore", class_obscure, cmd_record_restore,
_("Restore the execution log from a file.\n\
Argument is filename. File must be created with 'record save'."),
&record_cmdlist);
set_cmd_completer (c, filename_completer);
add_cmd ("delete", class_obscure, cmd_record_delete, add_cmd ("delete", class_obscure, cmd_record_delete,
_("Delete the rest of execution log and start recording it anew."), _("Delete the rest of execution log and start recording it anew."),