diff options
author | Michael Snyder <msnyder@vmware.com> | 2009-10-22 19:36:06 +0000 |
---|---|---|
committer | Michael Snyder <msnyder@vmware.com> | 2009-10-22 19:36:06 +0000 |
commit | 0156b21832b684643a30b598bf2ce5acae533768 (patch) | |
tree | 9d003571c49e1b89b6c78b178122ca91fda0927b /gdb | |
parent | 2009-10-22 Hui Zhu <teawater@gmail.com> (diff) | |
download | binutils-gdb-0156b21832b684643a30b598bf2ce5acae533768.tar.gz binutils-gdb-0156b21832b684643a30b598bf2ce5acae533768.tar.bz2 binutils-gdb-0156b21832b684643a30b598bf2ce5acae533768.zip |
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.
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/ChangeLog | 23 | ||||
-rw-r--r-- | gdb/gcore.c | 177 | ||||
-rw-r--r-- | gdb/gcore.h | 27 | ||||
-rw-r--r-- | gdb/record.c | 566 |
4 files changed, 711 insertions, 82 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 7583f91d1ca..2951bae2e4c 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -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> Michael Snyder <msnyder@vmware.com> diff --git a/gdb/gcore.c b/gdb/gcore.c index 5bc38e2ab5f..d1f8b4933cb 100644 --- a/gdb/gcore.c +++ b/gdb/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,30 @@ static enum bfd_architecture default_gcore_arch (void); 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). + Open a new bfd core file for output, and return the handle. */ -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). + 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. */ note_data = target_make_corefile_notes (obfd, ¬e_size); @@ -107,8 +96,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 +239,50 @@ derive_stack_segment (bfd_vma *bottom, bfd_vma *top) 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 the static data sections. Store its limits in *BOTTOM and *TOP. Return non-zero if successful. */ @@ -219,12 +290,10 @@ derive_stack_segment (bfd_vma *bottom, bfd_vma *top) 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 +328,9 @@ derive_heap_segment (bfd *abfd, bfd_vma *bottom, bfd_vma *top) } } - /* 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 +348,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)) diff --git a/gdb/gcore.h b/gdb/gcore.h new file mode 100644 index 00000000000..fc168d3e2ee --- /dev/null +++ b/gdb/gcore.h @@ -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 */ diff --git a/gdb/record.c b/gdb/record.c index 608673a68b7..2ff33d67fb7 100644 --- a/gdb/record.c +++ b/gdb/record.c @@ -23,10 +23,15 @@ #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> /* This module implements "target record", also known as "process @@ -54,6 +59,8 @@ #define RECORD_IS_REPLAY \ (record_list->next || execution_direction == EXEC_REVERSE) +#define RECORD_FILE_MAGIC netorder32(0x20091016) + /* These are the core structs of the process record functionality. 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) record_stop_at_limit = 0; 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 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); -} - struct record_message_args { struct regcache *regcache; enum target_signal signal; @@ -574,7 +581,7 @@ record_message (void *args) int ret; struct record_message_args *myargs = args; 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_tail = NULL; @@ -714,8 +721,8 @@ record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch, { entry->u.mem.mem_entry_not_accessible = 1; if (record_debug) - warning (_("Process record: error reading memory at " - "addr = %s len = %d."), + warning ("Process record: error reading memory at " + "addr = %s len = %d.", paddress (gdbarch, entry->u.mem.addr), 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; if (record_debug) - warning (_("Process record: error writing memory at " - "addr = %s len = %d."), + warning ("Process record: error writing memory at " + "addr = %s len = %d.", paddress (gdbarch, entry->u.mem.addr), entry->u.mem.len); } @@ -765,6 +772,8 @@ static int (*tmp_to_insert_breakpoint) (struct gdbarch *, static int (*tmp_to_remove_breakpoint) (struct gdbarch *, struct bp_target_info *); +static void record_restore (void); + /* Open the process record target. */ static void @@ -791,6 +800,7 @@ record_core_open_1 (char *name, int from_tty) } push_target (&record_core_ops); + record_restore (); } /* "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); if (record_debug) fprintf_unfiltered (gdb_stdlog, - _("Process record: failed to record " - "execution log.")); + "Process record: failed to record " + "execution log."); return -1; } 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); if (record_debug) fprintf_unfiltered (gdb_stdlog, - _("Process record: failed to record " - "execution log.")); + "Process record: failed to record " + "execution log."); return -1; } record_list->next = record_arch_list_head; @@ -1885,9 +1895,513 @@ info_record_command (char *args, int from_tty) 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, ®num, + 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, ®num, + 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 _initialize_record (void) { + struct cmd_list_element *c; + /* Init record_first. */ record_first.prev = NULL; record_first.next = NULL; @@ -1906,9 +2420,11 @@ _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, @@ -1923,6 +2439,18 @@ _initialize_record (void) "info record ", 0, &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, _("Delete the rest of execution log and start recording it anew."), |