gdb/
authorJan Kratochvil <jan.kratochvil@redhat.com>
Tue, 24 Jan 2012 13:46:55 +0000 (13:46 +0000)
committerJan Kratochvil <jan.kratochvil@redhat.com>
Tue, 24 Jan 2012 13:46:55 +0000 (13:46 +0000)
Fix watchpoints across inferior fork.
* amd64-linux-nat.c (update_debug_registers_callback): Update the
comment for linux_nat_iterate_watchpoint_lwps.
(amd64_linux_dr_set_control, amd64_linux_dr_set_addr): Use
linux_nat_iterate_watchpoint_lwps.
(amd64_linux_prepare_to_resume): New comment on Linux kernel.
* i386-linux-nat.c (update_debug_registers_callback): Update the
comment for linux_nat_iterate_watchpoint_lwps.
(i386_linux_dr_set_control, i386_linux_dr_set_addr): Use
linux_nat_iterate_watchpoint_lwps.
(i386_linux_prepare_to_resume): New comment on Linux kernel.
* i386-nat.c: Include inferior.h.
(dr_mirror): Remove.
(i386_inferior_data, struct i386_inferior_data)
(i386_inferior_data_get): New.
(i386_debug_reg_state): Use i386_inferior_data_get.
(i386_cleanup_dregs, i386_update_inferior_debug_regs)
(i386_insert_watchpoint, i386_remove_watchpoint)
(i386_stopped_data_address, i386_insert_hw_breakpoint)
(i386_remove_hw_breakpoint): New variable state, use
i386_debug_reg_state instead of DR_MIRROR.
* linux-nat.c (delete_lwp): New declaration.
(num_lwps): Move here from downwards.
(delete_lwp_cleanup): New.
(linux_child_follow_fork): Create new child_lp, call
linux_nat_new_thread and linux_nat_prepare_to_resume before calling
PTRACE_DETACH.
(num_lwps): Move upwards.
(linux_nat_iterate_watchpoint_lwps): New.
* linux-nat.h (linux_nat_iterate_watchpoint_lwps_ftype): New.
(linux_nat_iterate_watchpoint_lwps_ftype): New declaration.

gdb/testsuite/
Fix watchpoints across inferior fork.
* gdb.threads/watchpoint-fork-child.c: New file.
* gdb.threads/watchpoint-fork-mt.c: New file.
* gdb.threads/watchpoint-fork-parent.c: New file.
* gdb.threads/watchpoint-fork-st.c: New file.
* gdb.threads/watchpoint-fork.exp: New file.
* gdb.threads/watchpoint-fork.h: New file.

13 files changed:
gdb/ChangeLog
gdb/amd64-linux-nat.c
gdb/i386-linux-nat.c
gdb/i386-nat.c
gdb/linux-nat.c
gdb/linux-nat.h
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.threads/watchpoint-fork-child.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/watchpoint-fork-mt.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/watchpoint-fork-parent.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/watchpoint-fork-st.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/watchpoint-fork.exp [new file with mode: 0644]
gdb/testsuite/gdb.threads/watchpoint-fork.h [new file with mode: 0644]

index 02913aa..caf460b 100644 (file)
@@ -1,3 +1,37 @@
+2012-01-24  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+       Fix watchpoints across inferior fork.
+       * amd64-linux-nat.c (update_debug_registers_callback): Update the
+       comment for linux_nat_iterate_watchpoint_lwps.
+       (amd64_linux_dr_set_control, amd64_linux_dr_set_addr): Use
+       linux_nat_iterate_watchpoint_lwps.
+       (amd64_linux_prepare_to_resume): New comment on Linux kernel.
+       * i386-linux-nat.c (update_debug_registers_callback): Update the
+       comment for linux_nat_iterate_watchpoint_lwps.
+       (i386_linux_dr_set_control, i386_linux_dr_set_addr): Use
+       linux_nat_iterate_watchpoint_lwps.
+       (i386_linux_prepare_to_resume): New comment on Linux kernel.
+       * i386-nat.c: Include inferior.h.
+       (dr_mirror): Remove.
+       (i386_inferior_data, struct i386_inferior_data)
+       (i386_inferior_data_get): New.
+       (i386_debug_reg_state): Use i386_inferior_data_get.
+       (i386_cleanup_dregs, i386_update_inferior_debug_regs)
+       (i386_insert_watchpoint, i386_remove_watchpoint)
+       (i386_stopped_data_address, i386_insert_hw_breakpoint)
+       (i386_remove_hw_breakpoint): New variable state, use
+       i386_debug_reg_state instead of DR_MIRROR.
+       * linux-nat.c (delete_lwp): New declaration.
+       (num_lwps): Move here from downwards.
+       (delete_lwp_cleanup): New.
+       (linux_child_follow_fork): Create new child_lp, call
+       linux_nat_new_thread and linux_nat_prepare_to_resume before calling
+       PTRACE_DETACH.
+       (num_lwps): Move upwards.
+       (linux_nat_iterate_watchpoint_lwps): New.
+       * linux-nat.h (linux_nat_iterate_watchpoint_lwps_ftype): New.
+       (linux_nat_iterate_watchpoint_lwps_ftype): New declaration.
+
 2012-01-24  Joel Brobecker  <brobecker@adacore.com>
 
        GDB 7.4 released.
 2012-01-24  Joel Brobecker  <brobecker@adacore.com>
 
        GDB 7.4 released.
index a0bdbd7..f954483 100644 (file)
@@ -336,8 +336,8 @@ amd64_linux_dr_get_status (void)
   return amd64_linux_dr_get (inferior_ptid, DR_STATUS);
 }
 
   return amd64_linux_dr_get (inferior_ptid, DR_STATUS);
 }
 
-/* Callback for iterate_over_lwps.  Update the debug registers of
-   LWP.  */
+/* Callback for linux_nat_iterate_watchpoint_lwps.  Update the debug registers
+   of LWP.  */
 
 static int
 update_debug_registers_callback (struct lwp_info *lwp, void *arg)
 
 static int
 update_debug_registers_callback (struct lwp_info *lwp, void *arg)
@@ -363,9 +363,7 @@ update_debug_registers_callback (struct lwp_info *lwp, void *arg)
 static void
 amd64_linux_dr_set_control (unsigned long control)
 {
 static void
 amd64_linux_dr_set_control (unsigned long control)
 {
-  ptid_t pid_ptid = pid_to_ptid (ptid_get_pid (inferior_ptid));
-
-  iterate_over_lwps (pid_ptid, update_debug_registers_callback, NULL);
+  linux_nat_iterate_watchpoint_lwps (update_debug_registers_callback, NULL);
 }
 
 /* Set address REGNUM (zero based) to ADDR in all LWPs of the current
 }
 
 /* Set address REGNUM (zero based) to ADDR in all LWPs of the current
@@ -374,11 +372,9 @@ amd64_linux_dr_set_control (unsigned long control)
 static void
 amd64_linux_dr_set_addr (int regnum, CORE_ADDR addr)
 {
 static void
 amd64_linux_dr_set_addr (int regnum, CORE_ADDR addr)
 {
-  ptid_t pid_ptid = pid_to_ptid (ptid_get_pid (inferior_ptid));
-
   gdb_assert (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR);
 
   gdb_assert (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR);
 
-  iterate_over_lwps (pid_ptid, update_debug_registers_callback, NULL);
+  linux_nat_iterate_watchpoint_lwps (update_debug_registers_callback, NULL);
 }
 
 /* Called when resuming a thread.
 }
 
 /* Called when resuming a thread.
@@ -400,6 +396,13 @@ amd64_linux_prepare_to_resume (struct lwp_info *lwp)
       struct i386_debug_reg_state *state = i386_debug_reg_state ();
       int i;
 
       struct i386_debug_reg_state *state = i386_debug_reg_state ();
       int i;
 
+      /* On Linux kernel before 2.6.33 commit
+        72f674d203cd230426437cdcf7dd6f681dad8b0d
+        if you enable a breakpoint by the DR_CONTROL bits you need to have
+        already written the corresponding DR_FIRSTADDR...DR_LASTADDR registers.
+
+        Ensure DR_CONTROL gets written as the very last register here.  */
+
       for (i = DR_FIRSTADDR; i <= DR_LASTADDR; i++)
        if (state->dr_ref_count[i] > 0)
          {
       for (i = DR_FIRSTADDR; i <= DR_LASTADDR; i++)
        if (state->dr_ref_count[i] > 0)
          {
index 4b1b20f..38e7027 100644 (file)
@@ -708,8 +708,8 @@ i386_linux_dr_get_status (void)
   return i386_linux_dr_get (inferior_ptid, DR_STATUS);
 }
 
   return i386_linux_dr_get (inferior_ptid, DR_STATUS);
 }
 
-/* Callback for iterate_over_lwps.  Update the debug registers of
-   LWP.  */
+/* Callback for linux_nat_iterate_watchpoint_lwps.  Update the debug registers
+   of LWP.  */
 
 static int
 update_debug_registers_callback (struct lwp_info *lwp, void *arg)
 
 static int
 update_debug_registers_callback (struct lwp_info *lwp, void *arg)
@@ -735,9 +735,7 @@ update_debug_registers_callback (struct lwp_info *lwp, void *arg)
 static void
 i386_linux_dr_set_control (unsigned long control)
 {
 static void
 i386_linux_dr_set_control (unsigned long control)
 {
-  ptid_t pid_ptid = pid_to_ptid (ptid_get_pid (inferior_ptid));
-
-  iterate_over_lwps (pid_ptid, update_debug_registers_callback, NULL);
+  linux_nat_iterate_watchpoint_lwps (update_debug_registers_callback, NULL);
 }
 
 /* Set address REGNUM (zero based) to ADDR in all LWPs of the current
 }
 
 /* Set address REGNUM (zero based) to ADDR in all LWPs of the current
@@ -750,7 +748,7 @@ i386_linux_dr_set_addr (int regnum, CORE_ADDR addr)
 
   gdb_assert (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR);
 
 
   gdb_assert (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR);
 
-  iterate_over_lwps (pid_ptid, update_debug_registers_callback, NULL);
+  linux_nat_iterate_watchpoint_lwps (update_debug_registers_callback, NULL);
 }
 
 /* Called when resuming a thread.
 }
 
 /* Called when resuming a thread.
@@ -772,6 +770,9 @@ i386_linux_prepare_to_resume (struct lwp_info *lwp)
       struct i386_debug_reg_state *state = i386_debug_reg_state ();
       int i;
 
       struct i386_debug_reg_state *state = i386_debug_reg_state ();
       int i;
 
+      /* See amd64_linux_prepare_to_resume for Linux kernel note on
+        i386_linux_dr_set calls ordering.  */
+
       for (i = DR_FIRSTADDR; i <= DR_LASTADDR; i++)
        if (state->dr_ref_count[i] > 0)
          {
       for (i = DR_FIRSTADDR; i <= DR_LASTADDR; i++)
        if (state->dr_ref_count[i] > 0)
          {
index fa17823..e52de1e 100644 (file)
@@ -25,6 +25,7 @@
 #include "gdbcmd.h"
 #include "target.h"
 #include "gdb_assert.h"
 #include "gdbcmd.h"
 #include "target.h"
 #include "gdb_assert.h"
+#include "inferior.h"
 
 /* Support for hardware watchpoints and breakpoints using the i386
    debug registers.
 
 /* Support for hardware watchpoints and breakpoints using the i386
    debug registers.
@@ -170,14 +171,72 @@ i386_init_dregs (struct i386_debug_reg_state *state)
   state->dr_status_mirror  = 0;
 }
 
   state->dr_status_mirror  = 0;
 }
 
-/* The local mirror of the inferior's debug registers.  Currently this
-   is a global, but it should really be per-inferior.  */
-static struct i386_debug_reg_state dr_mirror;
+/* Per-inferior data key.  */
+static const struct inferior_data *i386_inferior_data;
+
+/* Per-inferior data.  */
+struct i386_inferior_data
+{
+  /* Copy of i386 hardware debug registers for performance reasons.  */
+  struct i386_debug_reg_state state;
+};
+
+/* Get data specific for INFERIOR_PTID LWP.  Return special data area
+   for processes being detached.  */
+
+static struct i386_inferior_data *
+i386_inferior_data_get (void)
+{
+  /* Intermediate patch stub.  */
+  static struct i386_inferior_data inf_data_local;
+  struct inferior *inf = current_inferior ();
+  struct i386_inferior_data *inf_data = &inf_data_local;
+
+  if (inf->pid != ptid_get_pid (inferior_ptid))
+    {
+      /* INFERIOR_PTID is being detached from the inferior INF.
+        Provide local cache specific for the detached LWP.  */
+
+      static struct i386_inferior_data detached_inf_data_local;
+      static int detached_inf_pid = -1;
+
+      if (detached_inf_pid != ptid_get_pid (inferior_ptid))
+       {
+         /* Reinitialize the local cache if INFERIOR_PTID is
+            different from the LWP last detached.
+            Linux kernel before 2.6.33 commit
+            72f674d203cd230426437cdcf7dd6f681dad8b0d
+            will inherit hardware debug registers from parent
+            on fork/vfork/clone.  Newer Linux kernels create such tasks with
+            zeroed debug registers.
+
+            GDB will remove all breakpoints (and watchpoints) from the forked
+            off process.  We also need to reset the debug registers in that
+            process to be compatible with the older Linux kernels.
+
+            Copy the debug registers mirrors into the new process so that all
+            breakpoints and watchpoints can be removed together.  The debug
+            registers mirror will become zeroed in the end before detaching
+            the forked off process.  */
+
+         detached_inf_pid = ptid_get_pid (inferior_ptid);
+         detached_inf_data_local = *inf_data;
+       }
+
+      return &detached_inf_data_local;
+    }
+
+  return inf_data;
+}
+
+/* Get debug registers state for INFERIOR_PTID, see
+   i386_inferior_data_get.  */
 
 struct i386_debug_reg_state *
 i386_debug_reg_state (void)
 {
 
 struct i386_debug_reg_state *
 i386_debug_reg_state (void)
 {
-  return &dr_mirror;
+  return &i386_inferior_data_get ()->state;
 }
 
 /* Whether or not to print the mirrored debug registers.  */
 }
 
 /* Whether or not to print the mirrored debug registers.  */
@@ -230,7 +289,9 @@ static int i386_handle_nonaligned_watchpoint (struct i386_debug_reg_state *state
 void
 i386_cleanup_dregs (void)
 {
 void
 i386_cleanup_dregs (void)
 {
-  i386_init_dregs (&dr_mirror);
+  struct i386_debug_reg_state *state = i386_debug_reg_state ();
+
+  i386_init_dregs (state);
 }
 
 /* Print the values of the mirrored debug registers.  This is called
 }
 
 /* Print the values of the mirrored debug registers.  This is called
@@ -494,20 +555,21 @@ Invalid value %d of operation in i386_handle_nonaligned_watchpoint.\n"),
 static void
 i386_update_inferior_debug_regs (struct i386_debug_reg_state *new_state)
 {
 static void
 i386_update_inferior_debug_regs (struct i386_debug_reg_state *new_state)
 {
+  struct i386_debug_reg_state *state = i386_debug_reg_state ();
   int i;
 
   ALL_DEBUG_REGISTERS (i)
     {
   int i;
 
   ALL_DEBUG_REGISTERS (i)
     {
-      if (I386_DR_VACANT (new_state, i) != I386_DR_VACANT (&dr_mirror, i))
+      if (I386_DR_VACANT (new_state, i) != I386_DR_VACANT (state, i))
        i386_dr_low.set_addr (i, new_state->dr_mirror[i]);
       else
        i386_dr_low.set_addr (i, new_state->dr_mirror[i]);
       else
-       gdb_assert (new_state->dr_mirror[i] == dr_mirror.dr_mirror[i]);
+       gdb_assert (new_state->dr_mirror[i] == state->dr_mirror[i]);
     }
 
     }
 
-  if (new_state->dr_control_mirror != dr_mirror.dr_control_mirror)
+  if (new_state->dr_control_mirror != state->dr_control_mirror)
     i386_dr_low.set_control (new_state->dr_control_mirror);
 
     i386_dr_low.set_control (new_state->dr_control_mirror);
 
-  dr_mirror = *new_state;
+  *state = *new_state;
 }
 
 /* Insert a watchpoint to watch a memory region which starts at
 }
 
 /* Insert a watchpoint to watch a memory region which starts at
@@ -518,10 +580,11 @@ static int
 i386_insert_watchpoint (CORE_ADDR addr, int len, int type,
                        struct expression *cond)
 {
 i386_insert_watchpoint (CORE_ADDR addr, int len, int type,
                        struct expression *cond)
 {
+  struct i386_debug_reg_state *state = i386_debug_reg_state ();
   int retval;
   /* Work on a local copy of the debug registers, and on success,
      commit the change back to the inferior.  */
   int retval;
   /* Work on a local copy of the debug registers, and on success,
      commit the change back to the inferior.  */
-  struct i386_debug_reg_state local_state = dr_mirror;
+  struct i386_debug_reg_state local_state = *state;
 
   if (type == hw_read)
     return 1; /* unsupported */
 
   if (type == hw_read)
     return 1; /* unsupported */
@@ -542,7 +605,7 @@ i386_insert_watchpoint (CORE_ADDR addr, int len, int type,
     i386_update_inferior_debug_regs (&local_state);
 
   if (maint_show_dr)
     i386_update_inferior_debug_regs (&local_state);
 
   if (maint_show_dr)
-    i386_show_dr (&dr_mirror, "insert_watchpoint", addr, len, type);
+    i386_show_dr (state, "insert_watchpoint", addr, len, type);
 
   return retval;
 }
 
   return retval;
 }
@@ -554,10 +617,11 @@ static int
 i386_remove_watchpoint (CORE_ADDR addr, int len, int type,
                        struct expression *cond)
 {
 i386_remove_watchpoint (CORE_ADDR addr, int len, int type,
                        struct expression *cond)
 {
+  struct i386_debug_reg_state *state = i386_debug_reg_state ();
   int retval;
   /* Work on a local copy of the debug registers, and on success,
      commit the change back to the inferior.  */
   int retval;
   /* Work on a local copy of the debug registers, and on success,
      commit the change back to the inferior.  */
-  struct i386_debug_reg_state local_state = dr_mirror;
+  struct i386_debug_reg_state local_state = *state;
 
   if (((len != 1 && len !=2 && len !=4) && !(TARGET_HAS_DR_LEN_8 && len == 8))
       || addr % len != 0)
 
   if (((len != 1 && len !=2 && len !=4) && !(TARGET_HAS_DR_LEN_8 && len == 8))
       || addr % len != 0)
@@ -575,7 +639,7 @@ i386_remove_watchpoint (CORE_ADDR addr, int len, int type,
     i386_update_inferior_debug_regs (&local_state);
 
   if (maint_show_dr)
     i386_update_inferior_debug_regs (&local_state);
 
   if (maint_show_dr)
-    i386_show_dr (&dr_mirror, "remove_watchpoint", addr, len, type);
+    i386_show_dr (state, "remove_watchpoint", addr, len, type);
 
   return retval;
 }
 
   return retval;
 }
@@ -586,11 +650,12 @@ i386_remove_watchpoint (CORE_ADDR addr, int len, int type,
 static int
 i386_region_ok_for_watchpoint (CORE_ADDR addr, int len)
 {
 static int
 i386_region_ok_for_watchpoint (CORE_ADDR addr, int len)
 {
+  struct i386_debug_reg_state *state = i386_debug_reg_state ();
   int nregs;
 
   /* Compute how many aligned watchpoints we would need to cover this
      region.  */
   int nregs;
 
   /* Compute how many aligned watchpoints we would need to cover this
      region.  */
-  nregs = i386_handle_nonaligned_watchpoint (&dr_mirror,
+  nregs = i386_handle_nonaligned_watchpoint (state,
                                             WP_COUNT, addr, len, hw_write);
   return nregs <= DR_NADDR ? 1 : 0;
 }
                                             WP_COUNT, addr, len, hw_write);
   return nregs <= DR_NADDR ? 1 : 0;
 }
@@ -602,6 +667,7 @@ i386_region_ok_for_watchpoint (CORE_ADDR addr, int len)
 static int
 i386_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
 {
 static int
 i386_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
 {
+  struct i386_debug_reg_state *state = i386_debug_reg_state ();
   CORE_ADDR addr = 0;
   int i;
   int rc = 0;
   CORE_ADDR addr = 0;
   int i;
   int rc = 0;
@@ -615,25 +681,24 @@ i386_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
   unsigned control = 0;
 
   /* In non-stop/async, threads can be running while we change the
   unsigned control = 0;
 
   /* In non-stop/async, threads can be running while we change the
-     global dr_mirror (and friends).  Say, we set a watchpoint, and
-     let threads resume.  Now, say you delete the watchpoint, or
-     add/remove watchpoints such that dr_mirror changes while threads
-     are running.  On targets that support non-stop,
-     inserting/deleting watchpoints updates the global dr_mirror only.
-     It does not update the real thread's debug registers; that's only
-     done prior to resume.  Instead, if threads are running when the
-     mirror changes, a temporary and transparent stop on all threads
-     is forced so they can get their copy of the debug registers
-     updated on re-resume.  Now, say, a thread hit a watchpoint before
-     having been updated with the new dr_mirror contents, and we
-     haven't yet handled the corresponding SIGTRAP.  If we trusted
-     dr_mirror below, we'd mistake the real trapped address (from the
-     last time we had updated debug registers in the thread) with
-     whatever was currently in dr_mirror.  So to fix this, dr_mirror
-     always represents intention, what we _want_ threads to have in
-     debug registers.  To get at the address and cause of the trap, we
-     need to read the state the thread still has in its debug
-     registers.
+     STATE (and friends).  Say, we set a watchpoint, and let threads
+     resume.  Now, say you delete the watchpoint, or add/remove
+     watchpoints such that STATE changes while threads are running.
+     On targets that support non-stop, inserting/deleting watchpoints
+     updates the STATE only.  It does not update the real thread's
+     debug registers; that's only done prior to resume.  Instead, if
+     threads are running when the mirror changes, a temporary and
+     transparent stop on all threads is forced so they can get their
+     copy of the debug registers updated on re-resume.  Now, say,
+     a thread hit a watchpoint before having been updated with the new
+     STATE contents, and we haven't yet handled the corresponding
+     SIGTRAP.  If we trusted STATE below, we'd mistake the real
+     trapped address (from the last time we had updated debug
+     registers in the thread) with whatever was currently in STATE.
+     So to fix this, STATE always represents intention, what we _want_
+     threads to have in debug registers.  To get at the address and
+     cause of the trap, we need to read the state the thread still has
+     in its debug registers.
 
      In sum, always get the current debug register values the current
      thread has, instead of trusting the global mirror.  If the thread
 
      In sum, always get the current debug register values the current
      thread has, instead of trusting the global mirror.  If the thread
@@ -663,11 +728,11 @@ i386_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
          addr = i386_dr_low.get_addr (i);
          rc = 1;
          if (maint_show_dr)
          addr = i386_dr_low.get_addr (i);
          rc = 1;
          if (maint_show_dr)
-           i386_show_dr (&dr_mirror, "watchpoint_hit", addr, -1, hw_write);
+           i386_show_dr (state, "watchpoint_hit", addr, -1, hw_write);
        }
     }
   if (maint_show_dr && addr == 0)
        }
     }
   if (maint_show_dr && addr == 0)
-    i386_show_dr (&dr_mirror, "stopped_data_addr", 0, 0, hw_write);
+    i386_show_dr (state, "stopped_data_addr", 0, 0, hw_write);
 
   if (rc)
     *addr_p = addr;
 
   if (rc)
     *addr_p = addr;
@@ -687,11 +752,12 @@ static int
 i386_insert_hw_breakpoint (struct gdbarch *gdbarch,
                           struct bp_target_info *bp_tgt)
 {
 i386_insert_hw_breakpoint (struct gdbarch *gdbarch,
                           struct bp_target_info *bp_tgt)
 {
+  struct i386_debug_reg_state *state = i386_debug_reg_state ();
   unsigned len_rw = i386_length_and_rw_bits (1, hw_execute);
   CORE_ADDR addr = bp_tgt->placed_address;
   /* Work on a local copy of the debug registers, and on success,
      commit the change back to the inferior.  */
   unsigned len_rw = i386_length_and_rw_bits (1, hw_execute);
   CORE_ADDR addr = bp_tgt->placed_address;
   /* Work on a local copy of the debug registers, and on success,
      commit the change back to the inferior.  */
-  struct i386_debug_reg_state local_state = dr_mirror;
+  struct i386_debug_reg_state local_state = *state;
   int retval = i386_insert_aligned_watchpoint (&local_state,
                                               addr, len_rw) ? EBUSY : 0;
 
   int retval = i386_insert_aligned_watchpoint (&local_state,
                                               addr, len_rw) ? EBUSY : 0;
 
@@ -699,7 +765,7 @@ i386_insert_hw_breakpoint (struct gdbarch *gdbarch,
     i386_update_inferior_debug_regs (&local_state);
 
   if (maint_show_dr)
     i386_update_inferior_debug_regs (&local_state);
 
   if (maint_show_dr)
-    i386_show_dr (&dr_mirror, "insert_hwbp", addr, 1, hw_execute);
+    i386_show_dr (state, "insert_hwbp", addr, 1, hw_execute);
 
   return retval;
 }
 
   return retval;
 }
@@ -711,11 +777,12 @@ static int
 i386_remove_hw_breakpoint (struct gdbarch *gdbarch,
                           struct bp_target_info *bp_tgt)
 {
 i386_remove_hw_breakpoint (struct gdbarch *gdbarch,
                           struct bp_target_info *bp_tgt)
 {
+  struct i386_debug_reg_state *state = i386_debug_reg_state ();
   unsigned len_rw = i386_length_and_rw_bits (1, hw_execute);
   CORE_ADDR addr = bp_tgt->placed_address;
   /* Work on a local copy of the debug registers, and on success,
      commit the change back to the inferior.  */
   unsigned len_rw = i386_length_and_rw_bits (1, hw_execute);
   CORE_ADDR addr = bp_tgt->placed_address;
   /* Work on a local copy of the debug registers, and on success,
      commit the change back to the inferior.  */
-  struct i386_debug_reg_state local_state = dr_mirror;
+  struct i386_debug_reg_state local_state = *state;
   int retval = i386_remove_aligned_watchpoint (&local_state,
                                               addr, len_rw);
 
   int retval = i386_remove_aligned_watchpoint (&local_state,
                                               addr, len_rw);
 
@@ -723,7 +790,7 @@ i386_remove_hw_breakpoint (struct gdbarch *gdbarch,
     i386_update_inferior_debug_regs (&local_state);
 
   if (maint_show_dr)
     i386_update_inferior_debug_regs (&local_state);
 
   if (maint_show_dr)
-    i386_show_dr (&dr_mirror, "remove_hwbp", addr, 1, hw_execute);
+    i386_show_dr (state, "remove_hwbp", addr, 1, hw_execute);
 
   return retval;
 }
 
   return retval;
 }
index 50d4efd..6aab087 100644 (file)
@@ -288,6 +288,7 @@ static void restore_child_signals_mask (sigset_t *prev_mask);
 struct lwp_info;
 static struct lwp_info *add_lwp (ptid_t ptid);
 static void purge_lwp_list (int pid);
 struct lwp_info;
 static struct lwp_info *add_lwp (ptid_t ptid);
 static void purge_lwp_list (int pid);
+static void delete_lwp (ptid_t ptid);
 static struct lwp_info *find_lwp_pid (ptid_t ptid);
 
 \f
 static struct lwp_info *find_lwp_pid (ptid_t ptid);
 
 \f
@@ -584,6 +585,31 @@ linux_child_post_startup_inferior (ptid_t ptid)
   linux_enable_tracesysgood (ptid);
 }
 
   linux_enable_tracesysgood (ptid);
 }
 
+/* Return the number of known LWPs in the tgid given by PID.  */
+
+static int
+num_lwps (int pid)
+{
+  int count = 0;
+  struct lwp_info *lp;
+
+  for (lp = lwp_list; lp; lp = lp->next)
+    if (ptid_get_pid (lp->ptid) == pid)
+      count++;
+
+  return count;
+}
+
+/* Call delete_lwp with prototype compatible for make_cleanup.  */
+
+static void
+delete_lwp_cleanup (void *lp_voidp)
+{
+  struct lwp_info *lp = lp_voidp;
+
+  delete_lwp (lp->ptid);
+}
+
 static int
 linux_child_follow_fork (struct target_ops *ops, int follow_child)
 {
 static int
 linux_child_follow_fork (struct target_ops *ops, int follow_child)
 {
@@ -630,6 +656,8 @@ holding the child stopped.  Try \"set detach-on-fork\" or \
       /* Detach new forked process?  */
       if (detach_fork)
        {
       /* Detach new forked process?  */
       if (detach_fork)
        {
+         struct cleanup *old_chain;
+
          /* Before detaching from the child, remove all breakpoints
             from it.  If we forked, then this has already been taken
             care of by infrun.c.  If we vforked however, any
          /* Before detaching from the child, remove all breakpoints
             from it.  If we forked, then this has already been taken
             care of by infrun.c.  If we vforked however, any
@@ -652,7 +680,28 @@ holding the child stopped.  Try \"set detach-on-fork\" or \
                                child_pid);
            }
 
                                child_pid);
            }
 
+         old_chain = save_inferior_ptid ();
+         inferior_ptid = ptid_build (child_pid, child_pid, 0);
+
+         child_lp = add_lwp (inferior_ptid);
+         child_lp->stopped = 1;
+         child_lp->last_resume_kind = resume_stop;
+         make_cleanup (delete_lwp_cleanup, child_lp);
+
+         /* CHILD_LP has new PID, therefore linux_nat_new_thread is not called for it.
+            See i386_inferior_data_get for the Linux kernel specifics.
+            Ensure linux_nat_prepare_to_resume will reset the hardware debug
+            registers.  It is done by the linux_nat_new_thread call, which is
+            being skipped in add_lwp above for the first lwp of a pid.  */
+         gdb_assert (num_lwps (GET_PID (child_lp->ptid)) == 1);
+         if (linux_nat_new_thread != NULL)
+           linux_nat_new_thread (child_lp);
+
+         if (linux_nat_prepare_to_resume != NULL)
+           linux_nat_prepare_to_resume (child_lp);
          ptrace (PTRACE_DETACH, child_pid, 0, 0);
          ptrace (PTRACE_DETACH, child_pid, 0, 0);
+
+         do_cleanups (old_chain);
        }
       else
        {
        }
       else
        {
@@ -1111,21 +1160,6 @@ purge_lwp_list (int pid)
     }
 }
 
     }
 }
 
-/* Return the number of known LWPs in the tgid given by PID.  */
-
-static int
-num_lwps (int pid)
-{
-  int count = 0;
-  struct lwp_info *lp;
-
-  for (lp = lwp_list; lp; lp = lp->next)
-    if (ptid_get_pid (lp->ptid) == pid)
-      count++;
-
-  return count;
-}
-
 /* Add the LWP specified by PID to the list.  Return a pointer to the
    structure describing the new LWP.  The LWP should already be stopped
    (with an exception for the very first LWP).  */
 /* Add the LWP specified by PID to the list.  Return a pointer to the
    structure describing the new LWP.  The LWP should already be stopped
    (with an exception for the very first LWP).  */
@@ -1235,6 +1269,46 @@ iterate_over_lwps (ptid_t filter,
   return NULL;
 }
 
   return NULL;
 }
 
+/* Iterate like iterate_over_lwps does except when forking-off a child call
+   CALLBACK with CALLBACK_DATA specifically only for that new child PID.  */
+
+void
+linux_nat_iterate_watchpoint_lwps
+  (linux_nat_iterate_watchpoint_lwps_ftype callback, void *callback_data)
+{
+  int inferior_pid = ptid_get_pid (inferior_ptid);
+  struct inferior *inf = current_inferior ();
+
+  if (inf->pid == inferior_pid)
+    {
+      /* Iterate all the threads of the current inferior.  Without specifying
+        INFERIOR_PID it would iterate all threads of all inferiors, which is
+        inappropriate for watchpoints.  */
+
+      iterate_over_lwps (pid_to_ptid (inferior_pid), callback, callback_data);
+    }
+  else
+    {
+      /* Detaching a new child PID temporarily present in INFERIOR_PID.  */
+
+      struct lwp_info *child_lp;
+      struct cleanup *old_chain;
+      pid_t child_pid = GET_PID (inferior_ptid);
+      ptid_t child_ptid = ptid_build (child_pid, child_pid, 0);
+
+      gdb_assert (!is_lwp (inferior_ptid));
+      gdb_assert (find_lwp_pid (child_ptid) == NULL);
+      child_lp = add_lwp (child_ptid);
+      child_lp->stopped = 1;
+      child_lp->last_resume_kind = resume_stop;
+      old_chain = make_cleanup (delete_lwp_cleanup, child_lp);
+
+      callback (child_lp, callback_data);
+
+      do_cleanups (old_chain);
+    }
+}
+
 /* Update our internal state when changing from one checkpoint to
    another indicated by NEW_PTID.  We can only switch single-threaded
    applications, so we only create one new LWP, and the previous list
 /* Update our internal state when changing from one checkpoint to
    another indicated by NEW_PTID.  We can only switch single-threaded
    applications, so we only create one new LWP, and the previous list
index d9dc71b..6217370 100644 (file)
@@ -158,6 +158,12 @@ struct lwp_info *iterate_over_lwps (ptid_t filter,
                                                     void *), 
                                    void *data);
 
                                                     void *), 
                                    void *data);
 
+typedef int (*linux_nat_iterate_watchpoint_lwps_ftype) (struct lwp_info *lwp,
+                                                       void *arg);
+
+extern void linux_nat_iterate_watchpoint_lwps
+  (linux_nat_iterate_watchpoint_lwps_ftype callback, void *callback_data);
+
 /* Create a prototype generic GNU/Linux target.  The client can
    override it with local methods.  */
 struct target_ops * linux_target (void);
 /* Create a prototype generic GNU/Linux target.  The client can
    override it with local methods.  */
 struct target_ops * linux_target (void);
index d92493d..63cfb9c 100644 (file)
@@ -1,3 +1,13 @@
+2012-01-24  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+       Fix watchpoints across inferior fork.
+       * gdb.threads/watchpoint-fork-child.c: New file.
+       * gdb.threads/watchpoint-fork-mt.c: New file.
+       * gdb.threads/watchpoint-fork-parent.c: New file.
+       * gdb.threads/watchpoint-fork-st.c: New file.
+       * gdb.threads/watchpoint-fork.exp: New file.
+       * gdb.threads/watchpoint-fork.h: New file.
+
 2012-01-23  Pedro Alves  <palves@redhat.com>
 
        * gdb.base/call-signal-resume.exp: Allow output after "return".
 2012-01-23  Pedro Alves  <palves@redhat.com>
 
        * gdb.base/call-signal-resume.exp: Allow output after "return".
diff --git a/gdb/testsuite/gdb.threads/watchpoint-fork-child.c b/gdb/testsuite/gdb.threads/watchpoint-fork-child.c
new file mode 100644 (file)
index 0000000..7a7e07f
--- /dev/null
@@ -0,0 +1,129 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+   Copyright 2012 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 2 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, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "watchpoint-fork.h"
+
+/* `pid_t' may not be available.  */
+
+static volatile int usr1_got;
+
+static void
+handler_usr1 (int signo)
+{
+  usr1_got++;
+}
+
+void
+forkoff (int nr)
+{
+  int child, save_parent = getpid ();
+  int i;
+  struct sigaction act, oldact;
+#ifdef THREAD
+  void *thread_result;
+#endif
+
+  memset (&act, 0, sizeof act);
+  act.sa_flags = SA_RESTART;
+  act.sa_handler = handler_usr1;
+  sigemptyset (&act.sa_mask);
+  i = sigaction (SIGUSR1, &act, &oldact);
+  assert (i == 0);
+
+  child = fork ();
+  switch (child)
+    {
+    case -1:
+      assert (0);
+    default:
+      printf ("parent%d: %d\n", nr, (int) child);
+
+      /* Sleep for a while to possibly get incorrectly ATTACH_THREADed by GDB
+        tracing the child fork with no longer valid thread/lwp entries of the
+        parent.  */
+
+      i = sleep (2);
+      assert (i == 0);
+
+      /* We must not get caught here (against a forgotten breakpoint).  */
+
+      var++;
+      marker ();
+
+#ifdef THREAD
+      /* And neither got caught our thread.  */
+
+      step = 99;
+      i = pthread_join (thread, &thread_result);
+      assert (i == 0);
+      assert (thread_result == (void *) 99UL);
+#endif
+
+      /* Be sure our child knows we did not get caught above.  */
+
+      i = kill (child, SIGUSR1);
+      assert (i == 0);
+
+      /* Sleep for a while to check GDB's `info threads' no longer tracks us in
+        the child fork.  */
+
+      i = sleep (2);
+      assert (i == 0);
+
+      _exit (0);
+    case 0:
+      printf ("child%d: %d\n", nr, (int) getpid ());
+
+      /* Let the parent signal us about its success.  Be careful of races.  */
+
+      for (;;)
+       {
+         /* Parent either died (and USR1_GOT is zero) or it succeeded.  */
+         if (getppid () != save_parent)
+           break;
+         if (kill (getppid (), 0) != 0)
+           break;
+         /* Parent succeeded?  */
+         if (usr1_got)
+           break;
+
+#ifdef THREAD
+         i = pthread_yield ();
+         assert (i == 0);
+#endif
+       }
+      assert (usr1_got);
+
+      /* We must get caught here (against a false watchpoint removal).  */
+
+      marker ();
+    }
+
+  i = sigaction (SIGUSR1, &oldact, NULL);
+  assert (i == 0);
+}
diff --git a/gdb/testsuite/gdb.threads/watchpoint-fork-mt.c b/gdb/testsuite/gdb.threads/watchpoint-fork-mt.c
new file mode 100644 (file)
index 0000000..eb07409
--- /dev/null
@@ -0,0 +1,174 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+   Copyright 2012 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 2 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, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <assert.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+#include <asm/unistd.h>
+#include <unistd.h>
+#define gettid() syscall (__NR_gettid)
+
+#include "watchpoint-fork.h"
+
+/* Non-atomic `var++' should not hurt as we synchronize the threads by the STEP
+   variable.  Hit-comments need to be duplicated there to catch both at-stops
+   and behind-stops, depending on the target.  */
+
+volatile int var;
+
+void
+marker (void)
+{
+}
+
+static void
+empty (void)
+{
+}
+
+static void
+mark_exit (void)
+{
+}
+
+pthread_t thread;
+volatile int step;
+
+static void *
+start (void *arg)
+{
+  int i;
+
+  if (step >= 3)
+    goto step_3;
+
+  while (step != 1)
+    {
+      i = pthread_yield ();
+      assert (i == 0);
+    }
+
+  var++;       /* validity-thread-B */
+  empty ();    /* validity-thread-B */
+  step = 2;
+  while (step != 3)
+    {
+      if (step == 99)
+       goto step_99;
+
+      i = pthread_yield ();
+      assert (i == 0);
+    }
+
+step_3:
+  if (step >= 5)
+    goto step_5;
+
+  var++;       /* after-fork1-B */
+  empty ();    /* after-fork1-B */
+  step = 4;
+  while (step != 5)
+    {
+      if (step == 99)
+       goto step_99;
+
+      i = pthread_yield ();
+      assert (i == 0);
+    }
+
+step_5:
+  var++;       /* after-fork2-B */
+  empty ();    /* after-fork2-B */
+  return (void *) 5UL;
+
+step_99:
+  /* We must not get caught here (against a forgotten breakpoint).  */
+  var++;
+  marker ();
+  return (void *) 99UL;
+}
+
+int
+main (void)
+{
+  int i;
+  void *thread_result;
+
+  setbuf (stdout, NULL);
+  printf ("main: %d\n", (int) gettid ());
+
+  /* General hardware breakpoints and watchpoints validity.  */
+  marker ();
+  var++;       /* validity-first */
+  empty ();    /* validity-first */
+
+  i = pthread_create (&thread, NULL, start, NULL);
+  assert (i == 0);
+
+  var++;       /* validity-thread-A */
+  empty ();    /* validity-thread-A */
+  step = 1;
+  while (step != 2)
+    {
+      i = pthread_yield ();
+      assert (i == 0);
+    }
+
+  /* Hardware watchpoints got disarmed here.  */
+  forkoff (1);
+
+  var++;       /* after-fork1-A */
+  empty ();    /* after-fork1-A */
+  step = 3;
+#ifdef FOLLOW_CHILD
+  /* Spawn new thread as it was deleted in the child of FORK.  */
+  i = pthread_create (&thread, NULL, start, NULL);
+  assert (i == 0);
+#endif
+  while (step != 4)
+    {
+      i = pthread_yield ();
+      assert (i == 0);
+    }
+
+  /* A sanity check for double hardware watchpoints removal.  */
+  forkoff (2);
+
+  var++;       /* after-fork2-A */
+  empty ();    /* after-fork2-A */
+  step = 5;
+#ifdef FOLLOW_CHILD
+  /* Spawn new thread as it was deleted in the child of FORK.  */
+  i = pthread_create (&thread, NULL, start, NULL);
+  assert (i == 0);
+#endif
+
+  i = pthread_join (thread, &thread_result);
+  assert (i == 0);
+  assert (thread_result == (void *) 5UL);
+
+  mark_exit ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/watchpoint-fork-parent.c b/gdb/testsuite/gdb.threads/watchpoint-fork-parent.c
new file mode 100644 (file)
index 0000000..9bbf438
--- /dev/null
@@ -0,0 +1,74 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+   Copyright 2012 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 2 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, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+#include "watchpoint-fork.h"
+
+void
+forkoff (int nr)
+{
+  pid_t child, pid_got;
+  int exit_code = 42 + nr;
+  int status, i;
+
+  child = fork ();
+  switch (child)
+    {
+    case -1:
+      assert (0);
+    case 0:
+      printf ("child%d: %d\n", nr, (int) getpid ());
+      /* Delay to get both the "child%d" and "parent%d" message printed without
+        a race breaking expect by its endless wait on `$gdb_prompt$':
+        Breakpoint 3, marker () at ../../../gdb/testsuite/gdb.threads/watchpoint-fork.c:33
+        33      }
+        (gdb) parent2: 14223  */
+      i = sleep (1);
+      assert (i == 0);
+
+      /* We must not get caught here (against a forgotten breakpoint).  */
+      var++;
+      marker ();
+
+      _exit (exit_code);
+    default:
+      printf ("parent%d: %d\n", nr, (int) child);
+      /* Delay to get both the "child%d" and "parent%d" message printed, see
+        above.  */
+      i = sleep (1);
+      assert (i == 0);
+
+      pid_got = wait (&status);
+      assert (pid_got == child);
+      assert (WIFEXITED (status));
+      assert (WEXITSTATUS (status) == exit_code);
+
+      /* We must get caught here (against a false watchpoint removal).  */
+      marker ();
+    }
+}
diff --git a/gdb/testsuite/gdb.threads/watchpoint-fork-st.c b/gdb/testsuite/gdb.threads/watchpoint-fork-st.c
new file mode 100644 (file)
index 0000000..17cc058
--- /dev/null
@@ -0,0 +1,61 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+   Copyright 2012 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 2 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, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <assert.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "watchpoint-fork.h"
+
+volatile int var;
+
+void
+marker (void)
+{
+}
+
+static void
+mark_exit (void)
+{
+}
+
+int
+main (void)
+{
+  setbuf (stdout, NULL);
+  printf ("main: %d\n", (int) getpid ());
+
+  /* General hardware breakpoints and watchpoints validity.  */
+  marker ();
+  var++;
+  /* Hardware watchpoints got disarmed here.  */
+  forkoff (1);
+  /* This watchpoint got lost before.  */
+  var++;
+  /* A sanity check for double hardware watchpoints removal.  */
+  forkoff (2);
+  var++;
+
+  mark_exit ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/watchpoint-fork.exp b/gdb/testsuite/gdb.threads/watchpoint-fork.exp
new file mode 100644 (file)
index 0000000..814fb02
--- /dev/null
@@ -0,0 +1,163 @@
+# Copyright 2012 Free Software Foundation, Inc.
+#
+# 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/>.
+
+# Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+set testfile watchpoint-fork
+
+if [is_remote target] {
+    kfail "remote/13584" "gdbserver does not support debugging across fork"
+    return
+}
+
+proc test {type symbol} {
+    global testfile objdir subdir srcdir gdb_prompt
+
+    global pf_prefix
+    set prefix_test $pf_prefix
+    lappend pf_prefix "$type:"
+    set prefix_mt $pf_prefix
+
+    set srcfile_type ${srcdir}/${subdir}/${testfile}-${type}.c
+
+
+    # no threads
+
+    set pf_prefix $prefix_mt
+    lappend pf_prefix "singlethreaded:"
+
+    set executable ${testfile}-${type}-st
+    set srcfile_main ${srcdir}/${subdir}/${testfile}-st.c
+    if { [gdb_compile "${srcfile_main} ${srcfile_type}" ${objdir}/${subdir}/${executable} executable [list debug additional_flags=-D$symbol]] != "" } {
+       untested ${testfile}.exp
+       set pf_prefix $prefix_test
+       return
+    }
+    clean_restart $executable
+
+    if [target_info exists gdb,no_hardware_watchpoints] {
+       # The software watchpoint functionality is in GDB an unrelated test.
+       gdb_test_no_output "set can-use-hw-watchpoints 0"
+    }
+
+    gdb_test "show detach-on-fork" "Whether gdb will detach the child of a fork is on\\."
+    gdb_test_no_output "set follow-fork-mode $type"
+    gdb_test "show follow-fork-mode" "Debugger response to a program call of fork or vfork is \"$type\"\\."
+    # Testcase uses it for the `follow-fork-mode child' type.
+    gdb_test "handle SIGUSR1 nostop noprint pass" "No\[ \t\]+No\[ \t\]+Yes.*"
+
+    if ![runto_main] {
+       set pf_prefix $prefix_test
+       return
+    }
+
+    gdb_test "watch var" "atchpoint \[0-9\]+: var" "Set the watchpoint"
+
+    # It is never hit but it should not be left over in the fork()ed-off child.
+    if [skip_hw_breakpoint_tests] {
+       set hbreak "break"
+    } else {
+       set hbreak "hbreak"
+    }
+    gdb_test "$hbreak marker"
+
+    gdb_breakpoint "mark_exit"
+
+    gdb_test "continue" \
+            "reakpoint \[0-9\]+, marker.*" "hardware breakpoints work"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 0.*New value = 1.*forkoff *\\(1\\).*" "watchpoints work"
+    gdb_test "continue" \
+            "reakpoint \[0-9\]+, marker.*" "breakpoint after the first fork"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 1.*New value = 2.*forkoff *\\(2\\).*" "watchpoint after the first fork"
+    gdb_test "continue" \
+            "reakpoint \[0-9\]+, marker.*" "breakpoint after the second fork"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 2.*New value = 3.*mark_exit \\(\\);" "watchpoint after the second fork"
+    gdb_test "continue" "Continuing\\..*\r\nBreakpoint \[0-9\]+, mark_exit .*" "finish"
+
+
+    # threads
+
+    if [target_info exists gdb,no_hardware_watchpoints] {
+       # Watchpoint hits would get detected in unexpected threads.
+       set pf_prefix $prefix_test
+       return
+    }
+
+    set pf_prefix $prefix_mt
+    lappend pf_prefix "multithreaded:"
+
+    set executable ${testfile}-${type}-mt
+    set srcfile_main ${srcdir}/${subdir}/${testfile}-mt.c
+    if { [gdb_compile_pthreads "${srcfile_main} ${srcfile_type}" ${objdir}/${subdir}/${executable} executable [list debug "additional_flags=-D$symbol -DTHREAD"]] != "" } {
+       untested ${testfile}.exp
+       set pf_prefix $prefix_test
+       return
+    }
+    clean_restart $executable
+
+    gdb_test_no_output "set follow-fork-mode $type"
+    # Testcase uses it for the `follow-fork-mode child' type.
+    gdb_test "handle SIGUSR1 nostop noprint pass" "No\[ \t\]+No\[ \t\]+Yes.*"
+
+    if ![runto_main] {
+       set pf_prefix $prefix_test
+       return
+    }
+
+    gdb_test "watch var" "atchpoint \[0-9\]+: var" "Set the watchpoint"
+
+    # It should not be left over in the fork()ed-off child.
+    gdb_test "$hbreak marker" {reakpoint [0-9]+.*}
+
+    gdb_breakpoint "mark_exit"
+
+    gdb_test "continue" \
+            "reakpoint \[0-9\]+, marker.*" "hardware breakpoints work"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 0.*New value = 1.*validity-first.*" "singlethread watchpoints work"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 1.*New value = 2.*validity-thread-A.*" "multithreaded watchpoints work at A"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 2.*New value = 3.*validity-thread-B.*" "multithreaded watchpoints work at B"
+    gdb_test "continue" \
+            "reakpoint \[0-9\]+, marker.*" "breakpoint (A) after the first fork"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 3.*New value = 4.*after-fork1-A.*" "watchpoint A after the first fork"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 4.*New value = 5.*after-fork1-B.*" "watchpoint B after the first fork"
+    gdb_test "continue" \
+            "reakpoint \[0-9\]+, marker.*" "breakpoint (A) after the second fork"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 5.*New value = 6.*after-fork2-A.*" "watchpoint A after the second fork"
+    gdb_test "continue" \
+            "atchpoint \[0-9\]+: var.*Old value = 6.*New value = 7.*after-fork2-B.*" "watchpoint B after the second fork"
+    gdb_test "continue" "Continuing\\..*\r\nBreakpoint \[0-9\]+, mark_exit .*" "finish"
+
+
+    # cleanup
+    set pf_prefix $prefix_test
+}
+
+test parent FOLLOW_PARENT
+
+# Only GNU/Linux is known to support `set follow-fork-mode child'.
+if [istarget "*-*-linux*"] {
+    test child FOLLOW_CHILD
+} else {
+    untested "${testfile}: child"
+}
diff --git a/gdb/testsuite/gdb.threads/watchpoint-fork.h b/gdb/testsuite/gdb.threads/watchpoint-fork.h
new file mode 100644 (file)
index 0000000..cb109fa
--- /dev/null
@@ -0,0 +1,32 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+   Copyright 2012 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 2 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, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifdef THREAD
+#include <pthread.h>
+
+extern volatile int step;
+extern pthread_t thread;
+#endif /* THREAD */
+
+extern volatile int var;
+
+extern void marker (void);
+extern void forkoff (int nr);