gdb/testsuite/
[deliverable/binutils-gdb.git] / gdb / record.c
index 65eaa111a7c4ae148e178d31245498a1ec886d7a..ac55cdf8a0d210b3455c95fe54e5e1c3bc7fa330 100644 (file)
@@ -1,6 +1,6 @@
 /* Process record and replay target for GDB, the GNU debugger.
 
-   Copyright (C) 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 2008-2012 Free Software Foundation, Inc.
 
    This file is part of GDB.
 
@@ -30,6 +30,9 @@
 #include "record.h"
 #include "elf-bfd.h"
 #include "gcore.h"
+#include "event-loop.h"
+#include "inf-loop.h"
+#include "gdb_bfd.h"
 
 #include <signal.h>
 
@@ -98,7 +101,7 @@ struct record_reg_entry
 
 struct record_end_entry
 {
-  enum target_signal sigval;
+  enum gdb_signal sigval;
   ULONGEST insn_num;
 };
 
@@ -207,7 +210,7 @@ static struct target_ops record_core_ops;
 /* The beneath function pointers.  */
 static struct target_ops *record_beneath_to_resume_ops;
 static void (*record_beneath_to_resume) (struct target_ops *, ptid_t, int,
-                                         enum target_signal);
+                                         enum gdb_signal);
 static struct target_ops *record_beneath_to_wait_ops;
 static ptid_t (*record_beneath_to_wait) (struct target_ops *, ptid_t,
                                         struct target_waitstatus *,
@@ -231,6 +234,7 @@ static int (*record_beneath_to_remove_breakpoint) (struct gdbarch *,
 static int (*record_beneath_to_stopped_by_watchpoint) (void);
 static int (*record_beneath_to_stopped_data_address) (struct target_ops *,
                                                      CORE_ADDR *);
+static void (*record_beneath_to_async) (void (*) (enum inferior_event_type, void *), void *);
 
 /* Alloc and free functions for record_reg, record_mem, and record_end 
    entries.  */
@@ -481,6 +485,20 @@ record_arch_list_add_reg (struct regcache *regcache, int regnum)
   return 0;
 }
 
+int
+record_read_memory (struct gdbarch *gdbarch,
+                   CORE_ADDR memaddr, gdb_byte *myaddr,
+                   ssize_t len)
+{
+  int ret = target_read_memory (memaddr, myaddr, len);
+
+  if (ret && record_debug)
+    printf_unfiltered (_("Process record: error reading memory "
+                        "at addr %s len = %ld.\n"),
+                      paddress (gdbarch, memaddr), (long) len);
+  return ret;
+}
+
 /* Record the value of a region of memory whose address is ADDR and
    length is LEN to record_arch_list.  */
 
@@ -500,13 +518,8 @@ record_arch_list_add_mem (CORE_ADDR addr, int len)
 
   rec = record_mem_alloc (addr, len);
 
-  if (target_read_memory (addr, record_get_loc (rec), len))
+  if (record_read_memory (target_gdbarch, addr, record_get_loc (rec), len))
     {
-      if (record_debug)
-       fprintf_unfiltered (gdb_stdlog,
-                           "Process record: error reading memory at "
-                           "addr = %s len = %d.\n",
-                           paddress (target_gdbarch, addr), len);
       record_mem_release (rec);
       return -1;
     }
@@ -528,7 +541,7 @@ record_arch_list_add_end (void)
                        "Process record: add end to arch list.\n");
 
   rec = record_end_alloc ();
-  rec->u.end.sigval = TARGET_SIGNAL_0;
+  rec->u.end.sigval = GDB_SIGNAL_0;
   rec->u.end.insn_num = ++record_insn_count;
 
   record_arch_list_add (rec);
@@ -578,7 +591,7 @@ record_arch_list_cleanups (void *ignore)
    record_arch_list, and add it to record_list.  */
 
 static int
-record_message (struct regcache *regcache, enum target_signal signal)
+record_message (struct regcache *regcache, enum gdb_signal signal)
 {
   int ret;
   struct gdbarch *gdbarch = get_regcache_arch (regcache);
@@ -618,7 +631,7 @@ record_message (struct regcache *regcache, enum target_signal signal)
       record_list->u.end.sigval = signal;
     }
 
-  if (signal == TARGET_SIGNAL_0
+  if (signal == GDB_SIGNAL_0
       || !gdbarch_process_record_signal_p (gdbarch))
     ret = gdbarch_process_record (gdbarch,
                                  regcache,
@@ -649,7 +662,7 @@ record_message (struct regcache *regcache, enum target_signal signal)
 
 struct record_message_args {
   struct regcache *regcache;
-  enum target_signal signal;
+  enum gdb_signal signal;
 };
 
 static int
@@ -662,7 +675,7 @@ record_message_wrapper (void *args)
 
 static int
 record_message_wrapper_safe (struct regcache *regcache,
-                             enum target_signal signal)
+                             enum gdb_signal signal)
 {
   struct record_message_args args;
 
@@ -735,15 +748,9 @@ record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
                                   paddress (gdbarch, entry->u.mem.addr),
                                   entry->u.mem.len);
 
-            if (target_read_memory (entry->u.mem.addr, mem, entry->u.mem.len))
-              {
-                entry->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);
-              }
+            if (record_read_memory (gdbarch,
+                                   entry->u.mem.addr, mem, entry->u.mem.len))
+             entry->u.mem.mem_entry_not_accessible = 1;
             else
               {
                 if (target_write_memory (entry->u.mem.addr, 
@@ -783,7 +790,7 @@ record_exec_insn (struct regcache *regcache, struct gdbarch *gdbarch,
 
 static struct target_ops *tmp_to_resume_ops;
 static void (*tmp_to_resume) (struct target_ops *, ptid_t, int,
-                             enum target_signal);
+                             enum gdb_signal);
 static struct target_ops *tmp_to_wait_ops;
 static ptid_t (*tmp_to_wait) (struct target_ops *, ptid_t,
                              struct target_waitstatus *,
@@ -806,9 +813,22 @@ static int (*tmp_to_remove_breakpoint) (struct gdbarch *,
                                        struct bp_target_info *);
 static int (*tmp_to_stopped_by_watchpoint) (void);
 static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
+static int (*tmp_to_stopped_data_address) (struct target_ops *, CORE_ADDR *);
+static void (*tmp_to_async) (void (*) (enum inferior_event_type, void *), void *);
 
 static void record_restore (void);
 
+/* Asynchronous signal handle registered as event loop source for when
+   we have pending events ready to be passed to the core.  */
+
+static struct async_event_handler *record_async_inferior_event_token;
+
+static void
+record_async_inferior_event_handler (gdb_client_data data)
+{
+  inferior_event_handler (INF_REG_EVENT, NULL);
+}
+
 /* Open the process record target.  */
 
 static void
@@ -852,9 +872,6 @@ record_open_1 (char *name, int from_tty)
   if (non_stop)
     error (_("Process record target can't debug inferior in non-stop mode "
             "(non-stop)."));
-  if (target_async_permitted)
-    error (_("Process record target can't debug inferior in asynchronous "
-            "mode (target-async)."));
 
   if (!gdbarch_process_record_p (target_gdbarch))
     error (_("Process record: the current architecture doesn't support "
@@ -883,6 +900,8 @@ record_open_1 (char *name, int from_tty)
   push_target (&record_ops);
 }
 
+static void record_init_record_breakpoints (void);
+
 /* "to_open" target method.  Open the process record target.  */
 
 static void
@@ -911,6 +930,7 @@ record_open (char *name, int from_tty)
   tmp_to_remove_breakpoint = NULL;
   tmp_to_stopped_by_watchpoint = NULL;
   tmp_to_stopped_data_address = NULL;
+  tmp_to_async = NULL;
 
   /* Set the beneath function pointers.  */
   for (t = current_target.beneath; t != NULL; t = t->beneath)
@@ -943,6 +963,8 @@ record_open (char *name, int from_tty)
        tmp_to_stopped_by_watchpoint = t->to_stopped_by_watchpoint;
       if (!tmp_to_stopped_data_address)
        tmp_to_stopped_data_address = t->to_stopped_data_address;
+      if (!tmp_to_async)
+       tmp_to_async = t->to_async;
     }
   if (!tmp_to_xfer_partial)
     error (_("Could not find 'to_xfer_partial' method on the target stack."));
@@ -966,11 +988,19 @@ record_open (char *name, int from_tty)
   record_beneath_to_remove_breakpoint = tmp_to_remove_breakpoint;
   record_beneath_to_stopped_by_watchpoint = tmp_to_stopped_by_watchpoint;
   record_beneath_to_stopped_data_address = tmp_to_stopped_data_address;
+  record_beneath_to_async = tmp_to_async;
 
   if (core_bfd)
     record_core_open_1 (name, from_tty);
   else
     record_open_1 (name, from_tty);
+
+  /* Register extra event sources in the event loop.  */
+  record_async_inferior_event_token
+    = create_async_event_handler (record_async_inferior_event_handler,
+                                 NULL);
+
+  record_init_record_breakpoints ();
 }
 
 /* "to_close" target method.  Close the process record target.  */
@@ -1002,17 +1032,43 @@ record_close (int quitting)
        }
       record_core_buf_list = NULL;
     }
+
+  if (record_async_inferior_event_token)
+    delete_async_event_handler (&record_async_inferior_event_token);
 }
 
 static int record_resume_step = 0;
 
+/* True if we've been resumed, and so each record_wait call should
+   advance execution.  If this is false, record_wait will return a
+   TARGET_WAITKIND_IGNORE.  */
+static int record_resumed = 0;
+
+/* The execution direction of the last resume we got.  This is
+   necessary for async mode.  Vis (order is not strictly accurate):
+
+   1. user has the global execution direction set to forward
+   2. user does a reverse-step command
+   3. record_resume is called with global execution direction
+      temporarily switched to reverse
+   4. GDB's execution direction is reverted back to forward
+   5. target record notifies event loop there's an event to handle
+   6. infrun asks the target which direction was it going, and switches
+      the global execution direction accordingly (to reverse)
+   7. infrun polls an event out of the record target, and handles it
+   8. GDB goes back to the event loop, and goto #4.
+*/
+static enum exec_direction_kind record_execution_dir = EXEC_FORWARD;
+
 /* "to_resume" target method.  Resume the process record target.  */
 
 static void
 record_resume (struct target_ops *ops, ptid_t ptid, int step,
-               enum target_signal signal)
+               enum gdb_signal signal)
 {
   record_resume_step = step;
+  record_resumed = 1;
+  record_execution_dir = execution_direction;
 
   if (!RECORD_IS_REPLAY)
     {
@@ -1051,9 +1107,22 @@ record_resume (struct target_ops *ops, ptid_t ptid, int step,
             }
         }
 
+      /* Make sure the target beneath reports all signals.  */
+      target_pass_signals (0, NULL);
+
       record_beneath_to_resume (record_beneath_to_resume_ops,
                                 ptid, step, signal);
     }
+
+  /* We are about to start executing the inferior (or simulate it),
+     let's register it with the event loop.  */
+  if (target_can_async_p ())
+    {
+      target_async (inferior_event_handler, 0);
+      /* Notify the event loop there's an event to wait for.  We do
+        most of the work in record_wait.  */
+      mark_async_event_handler (record_async_inferior_event_token);
+    }
 }
 
 static int record_get_sig = 0;
@@ -1100,17 +1169,27 @@ record_wait_cleanups (void *ignore)
    where to stop.  */
 
 static ptid_t
-record_wait (struct target_ops *ops,
-            ptid_t ptid, struct target_waitstatus *status,
-            int options)
+record_wait_1 (struct target_ops *ops,
+              ptid_t ptid, struct target_waitstatus *status,
+              int options)
 {
   struct cleanup *set_cleanups = record_gdb_operation_disable_set ();
 
   if (record_debug)
     fprintf_unfiltered (gdb_stdlog,
                        "Process record: record_wait "
-                       "record_resume_step = %d\n",
-                       record_resume_step);
+                       "record_resume_step = %d, record_resumed = %d, direction=%s\n",
+                       record_resume_step, record_resumed,
+                       record_execution_dir == EXEC_FORWARD ? "forward" : "reverse");
+
+  if (!record_resumed)
+    {
+      gdb_assert ((options & TARGET_WNOHANG) != 0);
+
+      /* No interesting event.  */
+      status->kind = TARGET_WAITKIND_IGNORE;
+      return minus_one_ptid;
+    }
 
   record_get_sig = 0;
   signal (SIGINT, record_sig_handler);
@@ -1134,16 +1213,24 @@ record_wait (struct target_ops *ops,
            {
              ret = record_beneath_to_wait (record_beneath_to_wait_ops,
                                            ptid, status, options);
+             if (status->kind == TARGET_WAITKIND_IGNORE)
+               {
+                 if (record_debug)
+                   fprintf_unfiltered (gdb_stdlog,
+                                       "Process record: record_wait "
+                                       "target beneath not done yet\n");
+                 return ret;
+               }
 
               if (single_step_breakpoints_inserted ())
                 remove_single_step_breakpoints ();
 
              if (record_resume_step)
-               return ret;
+               return ret;
 
              /* Is this a SIGTRAP?  */
              if (status->kind == TARGET_WAITKIND_STOPPED
-                 && status->value.sig == TARGET_SIGNAL_TRAP)
+                 && status->value.sig == GDB_SIGNAL_TRAP)
                {
                  struct regcache *regcache;
                  struct address_space *aspace;
@@ -1185,10 +1272,10 @@ record_wait (struct target_ops *ops,
                       int step = 1;
 
                      if (!record_message_wrapper_safe (regcache,
-                                                        TARGET_SIGNAL_0))
+                                                        GDB_SIGNAL_0))
                        {
                            status->kind = TARGET_WAITKIND_STOPPED;
-                           status->value.sig = TARGET_SIGNAL_0;
+                           status->value.sig = GDB_SIGNAL_0;
                            break;
                        }
 
@@ -1204,9 +1291,13 @@ record_wait (struct target_ops *ops,
                          set_executing (inferior_ptid, 1);
                        }
 
+                     if (record_debug)
+                       fprintf_unfiltered (gdb_stdlog,
+                                           "Process record: record_wait "
+                                           "issuing one more step in the target beneath\n");
                      record_beneath_to_resume (record_beneath_to_resume_ops,
                                                ptid, step,
-                                               TARGET_SIGNAL_0);
+                                               GDB_SIGNAL_0);
                      continue;
                    }
                }
@@ -1345,7 +1436,7 @@ record_wait (struct target_ops *ops,
                      continue_flag = 0;
                    }
                  /* Check target signal */
-                 if (record_list->u.end.sigval != TARGET_SIGNAL_0)
+                 if (record_list->u.end.sigval != GDB_SIGNAL_0)
                    /* FIXME: better way to check */
                    continue_flag = 0;
                }
@@ -1369,12 +1460,12 @@ record_wait (struct target_ops *ops,
 
 replay_out:
       if (record_get_sig)
-       status->value.sig = TARGET_SIGNAL_INT;
-      else if (record_list->u.end.sigval != TARGET_SIGNAL_0)
+       status->value.sig = GDB_SIGNAL_INT;
+      else if (record_list->u.end.sigval != GDB_SIGNAL_0)
        /* FIXME: better way to check */
        status->value.sig = record_list->u.end.sigval;
       else
-       status->value.sig = TARGET_SIGNAL_TRAP;
+       status->value.sig = GDB_SIGNAL_TRAP;
 
       discard_cleanups (old_cleanups);
     }
@@ -1385,6 +1476,24 @@ replay_out:
   return inferior_ptid;
 }
 
+static ptid_t
+record_wait (struct target_ops *ops,
+            ptid_t ptid, struct target_waitstatus *status,
+            int options)
+{
+  ptid_t return_ptid;
+
+  return_ptid = record_wait_1 (ops, ptid, status, options);
+  if (status->kind != TARGET_WAITKIND_IGNORE)
+    {
+      /* We're reporting a stop.  Make sure any spurious
+        target_wait(WNOHANG) doesn't advance the target until the
+        core wants us resumed again.  */
+      record_resumed = 0;
+    }
+  return return_ptid;
+}
+
 static int
 record_stopped_by_watchpoint (void)
 {
@@ -1620,24 +1729,97 @@ record_xfer_partial (struct target_ops *ops, enum target_object object,
                                          offset, len);
 }
 
-/* Behavior is conditional on RECORD_IS_REPLAY.
-   We will not actually insert or remove breakpoints when replaying,
-   nor when recording.  */
+/* This structure represents a breakpoint inserted while the record
+   target is active.  We use this to know when to install/remove
+   breakpoints in/from the target beneath.  For example, a breakpoint
+   may be inserted while recording, but removed when not replaying nor
+   recording.  In that case, the breakpoint had not been inserted on
+   the target beneath, so we should not try to remove it there.  */
+
+struct record_breakpoint
+{
+  /* The address and address space the breakpoint was set at.  */
+  struct address_space *address_space;
+  CORE_ADDR addr;
+
+  /* True when the breakpoint has been also installed in the target
+     beneath.  This will be false for breakpoints set during replay or
+     when recording.  */
+  int in_target_beneath;
+};
+
+typedef struct record_breakpoint *record_breakpoint_p;
+DEF_VEC_P(record_breakpoint_p);
+
+/* The list of breakpoints inserted while the record target is
+   active.  */
+VEC(record_breakpoint_p) *record_breakpoints = NULL;
+
+static void
+record_sync_record_breakpoints (struct bp_location *loc, void *data)
+{
+  if (loc->loc_type != bp_loc_software_breakpoint)
+      return;
+
+  if (loc->inserted)
+    {
+      struct record_breakpoint *bp = XNEW (struct record_breakpoint);
+
+      bp->addr = loc->target_info.placed_address;
+      bp->address_space = loc->target_info.placed_address_space;
+
+      bp->in_target_beneath = 1;
+
+      VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
+    }
+}
+
+/* Sync existing breakpoints to record_breakpoints.  */
+
+static void
+record_init_record_breakpoints (void)
+{
+  VEC_free (record_breakpoint_p, record_breakpoints);
+
+  iterate_over_bp_locations (record_sync_record_breakpoints);
+}
+
+/* Behavior is conditional on RECORD_IS_REPLAY.  We will not actually
+   insert or remove breakpoints in the real target when replaying, nor
+   when recording.  */
 
 static int
 record_insert_breakpoint (struct gdbarch *gdbarch,
                          struct bp_target_info *bp_tgt)
 {
+  struct record_breakpoint *bp;
+  int in_target_beneath = 0;
+
   if (!RECORD_IS_REPLAY)
     {
-      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
-      int ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt);
-
+      /* When recording, we currently always single-step, so we don't
+        really need to install regular breakpoints in the inferior.
+        However, we do have to insert software single-step
+        breakpoints, in case the target can't hardware step.  To keep
+        things single, we always insert.  */
+      struct cleanup *old_cleanups;
+      int ret;
+
+      old_cleanups = record_gdb_operation_disable_set ();
+      ret = record_beneath_to_insert_breakpoint (gdbarch, bp_tgt);
       do_cleanups (old_cleanups);
 
-      return ret;
+      if (ret != 0)
+       return ret;
+
+      in_target_beneath = 1;
     }
 
+  bp = XNEW (struct record_breakpoint);
+  bp->addr = bp_tgt->placed_address;
+  bp->address_space = bp_tgt->placed_address_space;
+  bp->in_target_beneath = in_target_beneath;
+  VEC_safe_push (record_breakpoint_p, record_breakpoints, bp);
   return 0;
 }
 
@@ -1647,17 +1829,35 @@ static int
 record_remove_breakpoint (struct gdbarch *gdbarch,
                          struct bp_target_info *bp_tgt)
 {
-  if (!RECORD_IS_REPLAY)
+  struct record_breakpoint *bp;
+  int ix;
+
+  for (ix = 0;
+       VEC_iterate (record_breakpoint_p, record_breakpoints, ix, bp);
+       ++ix)
     {
-      struct cleanup *old_cleanups = record_gdb_operation_disable_set ();
-      int ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt);
+      if (bp->addr == bp_tgt->placed_address
+         && bp->address_space == bp_tgt->placed_address_space)
+       {
+         if (bp->in_target_beneath)
+           {
+             struct cleanup *old_cleanups;
+             int ret;
 
-      do_cleanups (old_cleanups);
+             old_cleanups = record_gdb_operation_disable_set ();
+             ret = record_beneath_to_remove_breakpoint (gdbarch, bp_tgt);
+             do_cleanups (old_cleanups);
+
+             if (ret != 0)
+               return ret;
+           }
 
-      return ret;
+         VEC_unordered_remove (record_breakpoint_p, record_breakpoints, ix);
+         return 0;
+       }
     }
 
-  return 0;
+  gdb_assert_not_reached ("removing unknown breakpoint");
 }
 
 /* "to_can_execute_reverse" method for process record target.  */
@@ -1719,6 +1919,37 @@ record_goto_bookmark (gdb_byte *bookmark, int from_tty)
   return;
 }
 
+static void
+record_async (void (*callback) (enum inferior_event_type event_type,
+                               void *context), void *context)
+{
+  /* If we're on top of a line target (e.g., linux-nat, remote), then
+     set it to async mode as well.  Will be NULL if we're sitting on
+     top of the core target, for "record restore".  */
+  if (record_beneath_to_async != NULL)
+    record_beneath_to_async (callback, context);
+}
+
+static int
+record_can_async_p (void)
+{
+  /* We only enable async when the user specifically asks for it.  */
+  return target_async_permitted;
+}
+
+static int
+record_is_async_p (void)
+{
+  /* We only enable async when the user specifically asks for it.  */
+  return target_async_permitted;
+}
+
+static enum exec_direction_kind
+record_execution_direction (void)
+{
+  return record_execution_dir;
+}
+
 static void
 init_record_ops (void)
 {
@@ -1746,6 +1977,10 @@ init_record_ops (void)
   /* Add bookmark target methods.  */
   record_ops.to_get_bookmark = record_get_bookmark;
   record_ops.to_goto_bookmark = record_goto_bookmark;
+  record_ops.to_async = record_async;
+  record_ops.to_can_async_p = record_can_async_p;
+  record_ops.to_is_async_p = record_is_async_p;
+  record_ops.to_execution_direction = record_execution_direction;
   record_ops.to_magic = OPS_MAGIC;
 }
 
@@ -1753,9 +1988,21 @@ init_record_ops (void)
 
 static void
 record_core_resume (struct target_ops *ops, ptid_t ptid, int step,
-                    enum target_signal signal)
+                    enum gdb_signal signal)
 {
   record_resume_step = step;
+  record_resumed = 1;
+  record_execution_dir = execution_direction;
+
+  /* We are about to start executing the inferior (or simulate it),
+     let's register it with the event loop.  */
+  if (target_can_async_p ())
+    {
+      target_async (inferior_event_handler, 0);
+
+      /* Notify the event loop there's an event to wait for.  */
+      mark_async_event_handler (record_async_inferior_event_token);
+    }
 }
 
 /* "to_kill" method for prec over corefile.  */
@@ -1955,6 +2202,10 @@ init_record_core_ops (void)
   /* Add bookmark target methods.  */
   record_core_ops.to_get_bookmark = record_get_bookmark;
   record_core_ops.to_goto_bookmark = record_goto_bookmark;
+  record_core_ops.to_async = record_async;
+  record_core_ops.to_can_async_p = record_can_async_p;
+  record_core_ops.to_is_async_p = record_is_async_p;
+  record_core_ops.to_execution_direction = record_execution_direction;
   record_core_ops.to_magic = OPS_MAGIC;
 }
 
@@ -2394,7 +2645,7 @@ record_save_cleanups (void *data)
   bfd *obfd = data;
   char *pathname = xstrdup (bfd_get_filename (obfd));
 
-  bfd_close (obfd);
+  gdb_bfd_unref (obfd);
   unlink (pathname);
   xfree (pathname);
 }
@@ -2610,7 +2861,7 @@ cmd_record_save (char *args, int from_tty)
     }
 
   do_cleanups (set_cleanups);
-  bfd_close (obfd);
+  gdb_bfd_unref (obfd);
   discard_cleanups (old_cleanups);
 
   /* Succeeded.  */
@@ -2713,6 +2964,9 @@ cmd_record_goto (char *arg, int from_tty)
   print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC);
 }
 
+/* Provide a prototype to silence -Wmissing-prototypes.  */
+extern initialize_file_ftype _initialize_record;
+
 void
 _initialize_record (void)
 {
This page took 0.034071 seconds and 4 git commands to generate.