[patch] Fix disarmed hw watchpoints after inferior's fork()

Jan Kratochvil jan.kratochvil@redhat.com
Thu Jan 3 17:44:00 GMT 2008


Hi,

reply to:
	http://sourceware.org/ml/gdb-patches/2007-10/msg00608.html

This is a patch more following your description.  It works for:

* threaded fork-parents
* threaded fork-children (for new threads after fork)
* set follow-fork-mode child (parent is the default)

It does not work for
* set detach-on-fork off (on is the default)
as it has the threading support generally unsupported and it internal-errored
to me without any patches.

There is now very ugly TO_FOLLOW_FORK in each of the targets to prepare for
DETACH_BREAKPOINTS.  I expect it should be fixed to make it acceptable here
- a new target method TO_DETACH_BREAKPOINTS being passed `int lwp' and skipping
the current DETACH_BREAKPOINTS if such method exists?  I did not try this.

My committed patch yesterday
	[patch] Fix threaded inferiors in child after fork
	http://sourceware.org/ml/gdb-patches/2008-01/msg00020.html
hopefully has more pros than cons but it was found still insufficient for this
patch/testcase.  The second part of the attached patch fixes it up but the
CHECK_FOR_THREAD_DB looks a bit dangerous - it may expose some already-missing
THREAD_DB_FIND_NEW_THREADS calls before some of the ITERATE_OVER_THREADS calls.

The patch has no regressions (x86_64/i386/ia64/s390x/ppc64).
 * The threaded-watchpoints part has no testsuite changes.
 * The threads-in-the-fork-child part fixes staticthreads.exp as it no longer
   fails on some stale caught threads there.  (Still this patch would need the
   thread_db event hook to make it fully reliable.)

The ppc64 port FAILs some of the new testcases but the hardware watchpoints are
still known to be buggy at least in the kernel to be able to check it more.
	http://ozlabs.org/pipermail/linuxppc-dev/2007-November/046951.html


Regards,
Jan
-------------- next part --------------
2008-01-03  Jan Kratochvil  <jan.kratochvil@redhat.com>

	Fix watchpoints incl. those in threads after FORK.
	* i386-nat.c: Include "inferior.h".
	(i386_detach_breakpoints_pid, i386_detach_breakpoints)
	(i386_super_follow_fork, i386_follow_fork): New.
	(i386_remove_watchpoint): Check I386_DETACH_BREAKPOINTS_PID.
	* Makefile.in (i386-nat.o): Update dependencies.
	* config/i386/nm-i386.h (i386_super_follow_fork, i386_follow_fork): New
	prototypes.
	* amd64-linux-nat.c (amd64_linux_dr_set_control)
	(amd64_linux_dr_set_addr): Optionally override ALL_LWPS by an
	unthreaded INFERIOR_PTID.
	(_initialize_amd64_linux_nat): Setup TO_FOLLOW_FORK.
	* i386-linux-nat.c (i386_linux_dr_set_control, i386_linux_dr_set_addr):
	Optionally override ALL_LWPS by an unthreaded INFERIOR_PTID.
	(_initialize_i386_linux_nat): Setup TO_FOLLOW_FORK.
	* ia64-linux-nat.c (ia64_linux_detach_breakpoints_pid)
	(ia64_linux_detach_breakpoints, ia64_linux_super_follow_fork,
	(ia64_linux_follow_fork): New.
	(ia64_linux_remove_watchpoint): Check IA64_LINUX_DETACH_BREAKPOINTS_PID.
	(_initialize_ia64_linux_nat): Setup TO_FOLLOW_FORK.
	* ppc-linux-nat.c (ppc_linux_detach_breakpoints_pid)
	(ppc_linux_detach_breakpoints, ppc_linux_super_follow_fork)
	(ppc_linux_follow_fork): New.
	(ppc_linux_remove_watchpoint): Check PPC_LINUX_DETACH_BREAKPOINTS_PID.
	(_initialize_ppc_linux_nat): Setup TO_FOLLOW_FORK.
	* s390-nat.c (s390_fix_watch_points): Rename to ...
	(s390_fix_watch_points_list): ... here and change its PTID parameters
	to TID and AREA_LIST with the updated code for these parameters.
	(s390_fix_watch_points): Backward compatible new function.
	(s390_detach_breakpoints_pid, s390_detach_breakpoints)
	(s390_super_follow_fork, s390_follow_fork): New.
	(s390_remove_watchpoint): Check S390_DETACH_BREAKPOINTS_PID.
	(_initialize_s390_nat): Setup TO_FOLLOW_FORK.

	Fix threading initialization for FOLLOW-FORK-MODE CHILD.
	* linux-nat.c (linux_child_follow_fork): Call also INIT_THREAD_LIST.
	New comment for the call of CHECK_FOR_THREAD_DB.
	* linux-thread-db.c (check_for_thread_db): Remove the call of
	THREAD_DB_FIND_NEW_THREADS.

2008-01-03  Jan Kratochvil  <jan.kratochvil@redhat.com>

	* gdb.threads/watchpoint-fork-forkoff.c,
	gdb.threads/watchpoint-fork-mt.c, gdb.threads/watchpoint-fork.c,
	gdb.threads/watchpoint-fork.exp: New files.

--- ./gdb/Makefile.in	1 Jan 2008 22:53:09 -0000	1.971
+++ ./gdb/Makefile.in	3 Jan 2008 16:17:27 -0000
@@ -2219,7 +2219,8 @@ i386-linux-tdep.o: i386-linux-tdep.c $(d
 	$(value_h) $(regcache_h) $(inferior_h) $(osabi_h) $(reggroups_h) \
 	$(dwarf2_frame_h) $(gdb_string_h) $(i386_tdep_h) \
 	$(i386_linux_tdep_h) $(glibc_tdep_h) $(solib_svr4_h) $(symtab_h)
-i386-nat.o: i386-nat.c $(defs_h) $(breakpoint_h) $(command_h) $(gdbcmd_h)
+i386-nat.o: i386-nat.c $(defs_h) $(breakpoint_h) $(command_h) $(gdbcmd_h) \
+	$(inferior_h)
 i386nbsd-nat.o: i386nbsd-nat.c $(defs_h) $(gdbcore_h) $(regcache_h) \
 	$(target_h) $(i386_tdep_h) $(i386bsd_nat_h) $(nbsd_nat_h) \
 	$(bsd_kvm_h)
--- ./gdb/amd64-linux-nat.c	1 Jan 2008 22:53:09 -0000	1.19
+++ ./gdb/amd64-linux-nat.c	3 Jan 2008 16:17:27 -0000
@@ -284,25 +284,43 @@ amd64_linux_dr_set (ptid_t ptid, int reg
 void
 amd64_linux_dr_set_control (unsigned long control)
 {
-  struct lwp_info *lp;
-  ptid_t ptid;
-
   amd64_linux_dr[DR_CONTROL] = control;
-  ALL_LWPS (lp, ptid)
-    amd64_linux_dr_set (ptid, DR_CONTROL, control);
+
+  /* I386_DETACH_BREAKPOINTS may need to reset the registers on single process
+     not listed for ALL_LWPS.  */
+
+  if (ptid_get_lwp (inferior_ptid) == 0)
+    amd64_linux_dr_set (inferior_ptid, DR_CONTROL, control);
+  else
+    {
+      struct lwp_info *lp;
+      ptid_t ptid;
+
+      ALL_LWPS (lp, ptid)
+	amd64_linux_dr_set (ptid, DR_CONTROL, control);
+    }
 }
 
 void
 amd64_linux_dr_set_addr (int regnum, CORE_ADDR addr)
 {
-  struct lwp_info *lp;
-  ptid_t ptid;
-
   gdb_assert (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR);
 
   amd64_linux_dr[DR_FIRSTADDR + regnum] = addr;
-  ALL_LWPS (lp, ptid)
-    amd64_linux_dr_set (ptid, DR_FIRSTADDR + regnum, addr);
+
+  /* I386_DETACH_BREAKPOINTS may need to reset the registers on single process
+     not listed for ALL_LWPS.  */
+
+  if (ptid_get_lwp (inferior_ptid) == 0)
+    amd64_linux_dr_set (inferior_ptid, DR_FIRSTADDR + regnum, addr);
+  else
+    {
+      struct lwp_info *lp;
+      ptid_t ptid;
+
+      ALL_LWPS (lp, ptid)
+	amd64_linux_dr_set (ptid, DR_FIRSTADDR + regnum, addr);
+    }
 }
 
 void
@@ -427,6 +445,9 @@ _initialize_amd64_linux_nat (void)
   t->to_fetch_registers = amd64_linux_fetch_inferior_registers;
   t->to_store_registers = amd64_linux_store_inferior_registers;
 
+  i386_super_follow_fork = t->to_follow_fork;
+  t->to_follow_fork = i386_follow_fork;
+
   /* Register the target.  */
   linux_nat_add_target (t);
   linux_nat_set_new_thread (t, amd64_linux_new_thread);
--- ./gdb/i386-linux-nat.c	1 Jan 2008 22:53:10 -0000	1.85
+++ ./gdb/i386-linux-nat.c	3 Jan 2008 16:17:28 -0000
@@ -634,21 +634,42 @@ i386_linux_dr_set_control (unsigned long
   ptid_t ptid;
 
   i386_linux_dr[DR_CONTROL] = control;
-  ALL_LWPS (lp, ptid)
-    i386_linux_dr_set (ptid, DR_CONTROL, control);
+
+  /* I386_DETACH_BREAKPOINTS may need to reset the registers on single process
+     not listed for ALL_LWPS.  */
+
+  if (ptid_get_lwp (inferior_ptid) == 0)
+    i386_linux_dr_set (inferior_ptid, DR_CONTROL, control);
+  else
+    {
+      struct lwp_info *lp;
+      ptid_t ptid;
+
+      ALL_LWPS (lp, ptid)
+	i386_linux_dr_set (ptid, DR_CONTROL, control);
+    }
 }
 
 void
 i386_linux_dr_set_addr (int regnum, CORE_ADDR addr)
 {
-  struct lwp_info *lp;
-  ptid_t ptid;
-
   gdb_assert (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR);
 
   i386_linux_dr[DR_FIRSTADDR + regnum] = addr;
-  ALL_LWPS (lp, ptid)
-    i386_linux_dr_set (ptid, DR_FIRSTADDR + regnum, addr);
+
+  /* I386_DETACH_BREAKPOINTS may need to reset the registers on single process
+     not listed for ALL_LWPS.  */
+
+  if (ptid_get_lwp (inferior_ptid) == 0)
+    i386_linux_dr_set (inferior_ptid, DR_FIRSTADDR + regnum, addr);
+  else
+    {
+      struct lwp_info *lp;
+      ptid_t ptid;
+
+      ALL_LWPS (lp, ptid)
+	i386_linux_dr_set (ptid, DR_FIRSTADDR + regnum, addr);
+    }
 }
 
 void
@@ -831,6 +852,9 @@ _initialize_i386_linux_nat (void)
   t->to_fetch_registers = i386_linux_fetch_inferior_registers;
   t->to_store_registers = i386_linux_store_inferior_registers;
 
+  i386_super_follow_fork = t->to_follow_fork;
+  t->to_follow_fork = i386_follow_fork;
+
   /* Register the target.  */
   linux_nat_add_target (t);
   linux_nat_set_new_thread (t, i386_linux_new_thread);
--- ./gdb/i386-nat.c	1 Jan 2008 22:53:11 -0000	1.17
+++ ./gdb/i386-nat.c	3 Jan 2008 16:17:28 -0000
@@ -21,6 +21,7 @@
 #include "breakpoint.h"
 #include "command.h"
 #include "gdbcmd.h"
+#include "inferior.h"
 
 /* Support for hardware watchpoints and breakpoints using the i386
    debug registers.
@@ -520,6 +521,12 @@ i386_insert_watchpoint (CORE_ADDR addr, 
   return retval;
 }
 
+/* TO_FOLLOW_FORK stores here the PID under DETACH_BREAKPOINTS for the child
+   process of traced FORK.  We must clear such watchpoints only once during
+   DETACH_BREAKPOINTS.  */
+
+static int i386_detach_breakpoints_pid;
+
 /* Remove a watchpoint that watched the memory region which starts at
    address ADDR, whose length is LEN bytes, and for accesses of the
    type TYPE.  Return 0 on success, -1 on failure.  */
@@ -528,6 +535,11 @@ i386_remove_watchpoint (CORE_ADDR addr, 
 {
   int retval;
 
+  if (ptid_get_pid (inferior_ptid) == i386_detach_breakpoints_pid)
+    return 0;
+  /* FOLLOW-FORK-MODE CHILD runs later the CHILD with no restrictions.  */
+  i386_detach_breakpoints_pid = 0;
+
   if (((len != 1 && len !=2 && len !=4) && !(TARGET_HAS_DR_LEN_8 && len == 8))
       || addr % len != 0)
     retval = i386_handle_nonaligned_watchpoint (WP_REMOVE, addr, len, type);
@@ -544,6 +556,24 @@ i386_remove_watchpoint (CORE_ADDR addr, 
   return retval;
 }
 
+static void
+i386_detach_breakpoints (int detached_pid)
+{
+  struct cleanup *old_chain = save_inferior_ptid ();
+  int i;
+
+  i386_detach_breakpoints_pid = detached_pid;
+  /* Depend on `!is_lwp (inferior_ptid)' for the I386_* macros.  */
+  inferior_ptid = pid_to_ptid (detached_pid);
+
+  /* Do not touch any DR_MIRROR or DR_CONTROL_MIRROR mirrors here.  */
+  I386_DR_LOW_SET_CONTROL (0);
+  ALL_DEBUG_REGISTERS(i)
+    I386_DR_LOW_RESET_ADDR (i);
+
+  do_cleanups (old_chain);
+}
+
 /* Return non-zero if we can watch a memory region that starts at
    address ADDR and whose length is LEN bytes.  */
 
@@ -655,6 +685,39 @@ i386_remove_hw_breakpoint (struct bp_tar
 }
 
 #endif /* I386_USE_GENERIC_WATCHPOINTS */
+
+int (*i386_super_follow_fork) (struct target_ops *ops, int follow_child);
+
+/* We need to duplicate the LINUX_CHILD_FOLLOW_FORK behavior here and catch its
+   called DETACH_BREAKPOINTS to avoid corrupting our local registers mirror.  */
+
+int
+i386_follow_fork (struct target_ops *ops, int follow_child)
+{
+  ptid_t last_ptid;
+  struct target_waitstatus last_status;
+  int has_vforked;
+  int parent_pid, child_pid;
+
+  get_last_target_status (&last_ptid, &last_status);
+  has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED);
+  parent_pid = ptid_get_lwp (last_ptid);
+  if (parent_pid == 0)
+    parent_pid = ptid_get_pid (last_ptid);
+  child_pid = last_status.value.related_pid;
+
+  if (! follow_child)
+    {
+      i386_detach_breakpoints (child_pid);
+    }
+  else
+    {
+      if (! has_vforked)
+	i386_detach_breakpoints (child_pid);
+    }
+
+  return (*i386_super_follow_fork) (ops, follow_child);
+}
 
 
 /* Provide a prototype to silence -Wmissing-prototypes.  */
--- ./gdb/ia64-linux-nat.c	1 Jan 2008 22:53:11 -0000	1.44
+++ ./gdb/ia64-linux-nat.c	3 Jan 2008 16:17:29 -0000
@@ -583,6 +583,12 @@ ia64_linux_insert_watchpoint (CORE_ADDR 
   return 0;
 }
 
+/* TO_FOLLOW_FORK stores here the PID under DETACH_BREAKPOINTS for the child
+   process of traced FORK.  We must clear such watchpoints only once during
+   DETACH_BREAKPOINTS.  */
+
+static int ia64_linux_detach_breakpoints_pid;
+
 static int
 ia64_linux_remove_watchpoint (CORE_ADDR addr, int len, int type)
 {
@@ -590,6 +596,11 @@ ia64_linux_remove_watchpoint (CORE_ADDR 
   long dbr_addr, dbr_mask;
   int max_watchpoints = 4;
 
+  if (ptid_get_pid (inferior_ptid) == ia64_linux_detach_breakpoints_pid)
+    return 0;
+  /* FOLLOW-FORK-MODE CHILD runs later the CHILD with no restrictions.  */
+  ia64_linux_detach_breakpoints_pid = 0;
+
   if (len <= 0 || !is_power_of_2 (len))
     return -1;
 
@@ -617,6 +628,22 @@ ia64_linux_remove_watchpoint (CORE_ADDR 
 }
 
 static void
+ia64_linux_detach_breakpoints (int detached_pid)
+{
+  int idx, i;
+  long dbr_addr, dbr_mask;
+  int max_watchpoints = 4;
+
+  ia64_linux_detach_breakpoints_pid = detached_pid;
+
+  /* Do not touch any DEBUG_REGISTERS mirrors here.  */
+  dbr_addr = 0;
+  dbr_mask = 0;
+  for (idx = 0; idx < max_watchpoints; idx++)
+    store_debug_register_pair (ptid_build (detached_pid, 0, 0), idx, &dbr_addr, &dbr_mask);
+}
+
+static void
 ia64_linux_new_thread (ptid_t ptid)
 {
   int i, any;
@@ -805,6 +832,40 @@ ia64_linux_xfer_partial (struct target_o
 			     offset, len);
 }
 
+static int (*ia64_linux_super_follow_fork) (struct target_ops *ops,
+					    int follow_child);
+
+/* We need to duplicate the LINUX_CHILD_FOLLOW_FORK behavior here and catch its
+   called DETACH_BREAKPOINTS to avoid corrupting our local registers mirror.  */
+
+int
+ia64_linux_follow_fork (struct target_ops *ops, int follow_child)
+{
+  ptid_t last_ptid;
+  struct target_waitstatus last_status;
+  int has_vforked;
+  int parent_pid, child_pid;
+
+  get_last_target_status (&last_ptid, &last_status);
+  has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED);
+  parent_pid = ptid_get_lwp (last_ptid);
+  if (parent_pid == 0)
+    parent_pid = ptid_get_pid (last_ptid);
+  child_pid = last_status.value.related_pid;
+
+  if (! follow_child)
+    {
+      ia64_linux_detach_breakpoints (child_pid);
+    }
+  else
+    {
+      if (! has_vforked)
+	ia64_linux_detach_breakpoints (child_pid);
+    }
+
+  return (*ia64_linux_super_follow_fork) (ops, follow_child);
+}
+
 void _initialize_ia64_linux_nat (void);
 
 void
@@ -841,6 +902,9 @@ _initialize_ia64_linux_nat (void)
   t->to_insert_watchpoint = ia64_linux_insert_watchpoint;
   t->to_remove_watchpoint = ia64_linux_remove_watchpoint;
 
+  ia64_linux_super_follow_fork = t->to_follow_fork;
+  t->to_follow_fork = ia64_linux_follow_fork;
+
   /* Register the target.  */
   linux_nat_add_target (t);
   linux_nat_set_new_thread (t, ia64_linux_new_thread);
--- ./gdb/linux-nat.c	2 Jan 2008 13:36:37 -0000	1.72
+++ ./gdb/linux-nat.c	3 Jan 2008 16:17:32 -0000
@@ -516,7 +516,20 @@ linux_child_follow_fork (struct target_o
 	 target_detach (which does other necessary cleanup).  */
 
       push_target (ops);
+
+      /* INIT_THREAD_LIST is not directly required as the already detached
+	 threads in THREAD_LIST will be reclaimed by PRUNE_THREADS's
+	 LINUX_NAT_THREAD_ALIVE - on the other hand it cleans up the list.  */
+
+      init_thread_list ();
+
       linux_nat_switch_fork (inferior_ptid);
+
+      /* CHECK_FOR_THREAD_DB must not call THREAD_DB_FIND_NEW_THREADS as there
+	 is no libthread_db event on the post-fork child threads list
+	 reclaiming finish so we would find and ATTACH_THREAD the threads of
+	 our forked-off parent at this time.  */
+
       check_for_thread_db ();
 
       /* Reset breakpoints in the child as appropriate.  */
--- ./gdb/linux-thread-db.c	1 Jan 2008 22:53:11 -0000	1.36
+++ ./gdb/linux-thread-db.c	3 Jan 2008 16:17:32 -0000
@@ -647,7 +647,10 @@ check_for_thread_db (void)
       using_thread_db = 1;
 
       enable_thread_event_reporting ();
-      thread_db_find_new_threads ();
+      
+      /* THREAD_DB_FIND_NEW_THREADS does not have to be called right now as
+	 anyone interested in the list calls it to get the list fresh.  */
+
       break;
 
     default:
--- ./gdb/ppc-linux-nat.c	1 Jan 2008 22:53:12 -0000	1.76
+++ ./gdb/ppc-linux-nat.c	3 Jan 2008 16:17:33 -0000
@@ -847,6 +847,12 @@ ppc_linux_insert_watchpoint (CORE_ADDR a
   return 0;
 }
 
+/* TO_FOLLOW_FORK stores here the PID under DETACH_BREAKPOINTS for the child
+   process of traced FORK.  We must clear such watchpoints only once during
+   DETACH_BREAKPOINTS.  */
+
+static int ppc_linux_detach_breakpoints_pid;
+
 static int
 ppc_linux_remove_watchpoint (CORE_ADDR addr, int len, int rw)
 {
@@ -854,6 +860,11 @@ ppc_linux_remove_watchpoint (CORE_ADDR a
   ptid_t ptid;
   long dabr_value = 0;
 
+  if (ptid_get_pid (inferior_ptid) == ppc_linux_detach_breakpoints_pid)
+    return 0;
+  /* FOLLOW-FORK-MODE CHILD runs later the CHILD with no restrictions.  */
+  ppc_linux_detach_breakpoints_pid = 0;
+
   saved_dabr_value = 0;
   ALL_LWPS (lp, ptid)
     if (ptrace (PTRACE_SET_DEBUGREG, TIDGET (ptid), 0, saved_dabr_value) < 0)
@@ -862,6 +873,15 @@ ppc_linux_remove_watchpoint (CORE_ADDR a
 }
 
 static void
+ppc_linux_detach_breakpoints (int detached_pid)
+{
+  ppc_linux_detach_breakpoints_pid = detached_pid;
+
+  /* Do not touch the SAVED_DABR_VALUE mirror here.  */
+  ptrace (PTRACE_SET_DEBUGREG, detached_pid, 0, 0);
+}
+
+static void
 ppc_linux_new_thread (ptid_t ptid)
 {
   ptrace (PTRACE_SET_DEBUGREG, TIDGET (ptid), 0, saved_dabr_value);
@@ -976,6 +996,40 @@ ppc_linux_read_description (struct targe
   return NULL;
 }
 
+static int (*ppc_linux_super_follow_fork) (struct target_ops *ops,
+					   int follow_child);
+
+/* We need to duplicate the LINUX_CHILD_FOLLOW_FORK behavior here and catch its
+   called DETACH_BREAKPOINTS to avoid corrupting our local registers mirror.  */
+
+int
+ppc_linux_follow_fork (struct target_ops *ops, int follow_child)
+{
+  ptid_t last_ptid;
+  struct target_waitstatus last_status;
+  int has_vforked;
+  int parent_pid, child_pid;
+
+  get_last_target_status (&last_ptid, &last_status);
+  has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED);
+  parent_pid = ptid_get_lwp (last_ptid);
+  if (parent_pid == 0)
+    parent_pid = ptid_get_pid (last_ptid);
+  child_pid = last_status.value.related_pid;
+
+  if (! follow_child)
+    {
+      ppc_linux_detach_breakpoints (child_pid);
+    }
+  else
+    {
+      if (! has_vforked)
+	ppc_linux_detach_breakpoints (child_pid);
+    }
+
+  return (*ppc_linux_super_follow_fork) (ops, follow_child);
+}
+
 void _initialize_ppc_linux_nat (void);
 
 void
@@ -1000,6 +1054,9 @@ _initialize_ppc_linux_nat (void)
 
   t->to_read_description = ppc_linux_read_description;
 
+  ppc_linux_super_follow_fork = t->to_follow_fork;
+  t->to_follow_fork = ppc_linux_follow_fork;
+
   /* Register the target.  */
   linux_nat_add_target (t);
   linux_nat_set_new_thread (t, ppc_linux_new_thread);
--- ./gdb/s390-nat.c	7 Nov 2007 06:36:57 -0000	1.27
+++ ./gdb/s390-nat.c	3 Jan 2008 16:17:33 -0000
@@ -1,5 +1,5 @@
 /* S390 native-dependent code for GDB, the GNU debugger.
-   Copyright (C) 2001, 2003, 2004, 2005, 2006
+   Copyright (C) 2001, 2003, 2004, 2005, 2006, 2007, 2008
    Free Software Foundation, Inc
 
    Contributed by D.J. Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
@@ -283,21 +283,15 @@ s390_stopped_by_watchpoint (void)
 }
 
 static void
-s390_fix_watch_points (ptid_t ptid)
+s390_fix_watch_points_list (int tid, struct watch_area *area_list)
 {
-  int tid;
-
   per_struct per_info;
   ptrace_area parea;
 
   CORE_ADDR watch_lo_addr = (CORE_ADDR)-1, watch_hi_addr = 0;
   struct watch_area *area;
 
-  tid = TIDGET (ptid);
-  if (tid == 0)
-    tid = PIDGET (ptid);
-
-  for (area = watch_base; area; area = area->next)
+  for (area = area_list; area; area = area->next)
     {
       watch_lo_addr = min (watch_lo_addr, area->lo_addr);
       watch_hi_addr = max (watch_hi_addr, area->hi_addr);
@@ -309,7 +303,7 @@ s390_fix_watch_points (ptid_t ptid)
   if (ptrace (PTRACE_PEEKUSR_AREA, tid, &parea) < 0)
     perror_with_name (_("Couldn't retrieve watchpoint status"));
 
-  if (watch_base)
+  if (area_list)
     {
       per_info.control_regs.bits.em_storage_alteration = 1;
       per_info.control_regs.bits.storage_alt_space_ctl = 1;
@@ -326,6 +320,18 @@ s390_fix_watch_points (ptid_t ptid)
     perror_with_name (_("Couldn't modify watchpoint status"));
 }
 
+static void
+s390_fix_watch_points (ptid_t ptid)
+{
+  int tid;
+
+  tid = TIDGET (ptid);
+  if (tid == 0)
+    tid = PIDGET (ptid);
+
+  s390_fix_watch_points_list (tid, watch_base);
+}
+
 static int
 s390_insert_watchpoint (CORE_ADDR addr, int len, int type)
 {
@@ -347,6 +353,12 @@ s390_insert_watchpoint (CORE_ADDR addr, 
   return 0;
 }
 
+/* TO_FOLLOW_FORK stores here the PID under DETACH_BREAKPOINTS for the child
+   process of traced FORK.  We must clear such watchpoints only once during
+   DETACH_BREAKPOINTS.  */
+
+static int s390_detach_breakpoints_pid;
+
 static int
 s390_remove_watchpoint (CORE_ADDR addr, int len, int type)
 {
@@ -354,6 +366,11 @@ s390_remove_watchpoint (CORE_ADDR addr, 
   ptid_t ptid;
   struct watch_area *area, **parea;
 
+  if (ptid_get_pid (inferior_ptid) == s390_detach_breakpoints_pid)
+    return 0;
+  /* FOLLOW-FORK-MODE CHILD runs later the CHILD with no restrictions.  */
+  s390_detach_breakpoints_pid = 0;
+
   for (parea = &watch_base; *parea; parea = &(*parea)->next)
     if ((*parea)->lo_addr == addr
 	&& (*parea)->hi_addr == addr + len - 1)
@@ -375,6 +392,15 @@ s390_remove_watchpoint (CORE_ADDR addr, 
   return 0;
 }
 
+static void
+s390_detach_breakpoints (int detached_pid)
+{
+  s390_detach_breakpoints_pid = detached_pid;
+
+  /* Do not touch the WATCH_BASE here.  */
+  s390_fix_watch_points_list (detached_pid, NULL);
+}
+
 static int
 s390_can_use_hw_breakpoint (int type, int cnt, int othertype)
 {
@@ -387,6 +413,39 @@ s390_region_ok_for_hw_watchpoint (CORE_A
   return 1;
 }
 
+static int (*s390_super_follow_fork) (struct target_ops *ops, int follow_child);
+
+/* We need to duplicate the LINUX_CHILD_FOLLOW_FORK behavior here and catch its
+   called DETACH_BREAKPOINTS to avoid corrupting our local registers mirror.  */
+
+int
+s390_follow_fork (struct target_ops *ops, int follow_child)
+{
+  ptid_t last_ptid;
+  struct target_waitstatus last_status;
+  int has_vforked;
+  int parent_pid, child_pid;
+
+  get_last_target_status (&last_ptid, &last_status);
+  has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED);
+  parent_pid = ptid_get_lwp (last_ptid);
+  if (parent_pid == 0)
+    parent_pid = ptid_get_pid (last_ptid);
+  child_pid = last_status.value.related_pid;
+
+  if (! follow_child)
+    {
+      s390_detach_breakpoints (child_pid);
+    }
+  else
+    {
+      if (! has_vforked)
+	s390_detach_breakpoints (child_pid);
+    }
+
+  return (*s390_super_follow_fork) (ops, follow_child);
+}
+
 
 void _initialize_s390_nat (void);
 
@@ -410,6 +469,9 @@ _initialize_s390_nat (void)
   t->to_insert_watchpoint = s390_insert_watchpoint;
   t->to_remove_watchpoint = s390_remove_watchpoint;
 
+  s390_super_follow_fork = t->to_follow_fork;
+  t->to_follow_fork = s390_follow_fork;
+
   /* Register the target.  */
   linux_nat_add_target (t);
   linux_nat_set_new_thread (t, s390_fix_watch_points);
--- ./gdb/config/i386/nm-i386.h	1 Jan 2008 22:53:14 -0000	1.12
+++ ./gdb/config/i386/nm-i386.h	3 Jan 2008 16:17:33 -0000
@@ -110,6 +110,10 @@ extern int i386_stopped_by_watchpoint (v
 #define target_remove_hw_breakpoint(bp_tgt) \
   i386_remove_hw_breakpoint (bp_tgt)
 
+struct target_ops;
+extern int (*i386_super_follow_fork) (struct target_ops *ops, int follow_child);
+extern int i386_follow_fork (struct target_ops *ops, int follow_child);
+
 #endif /* I386_USE_GENERIC_WATCHPOINTS */
 
 #endif /* NM_I386_H */
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/watchpoint-fork-forkoff.c	3 Jan 2008 16:17:35 -0000
@@ -0,0 +1,160 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+   Copyright 2008
+   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>
+
+static void delay (void)
+{
+  int i = usleep (1000000 / 100);
+  assert (i == 0 || errno == EINTR);
+}
+
+#if defined FOLLOW_PARENT
+
+static void forkoff (int nr)
+{
+  pid_t child, pid_got;
+  int exit_code = 42 + nr;
+  int status;
+
+  child = fork ();
+  switch (child)
+    {
+    case -1:
+      assert (0);
+    case 0:
+      printf ("child%d: %d\n", nr, (int) getpid ());
+
+      /* We must not get caught here (against a forgotten breakpoint).  */
+      var++;
+      breakpoint ();
+
+      _exit (exit_code);
+    default:
+      printf ("parent%d: %d\n", nr, (int) child);
+      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).  */
+      breakpoint ();
+    }
+}
+
+#elif defined FOLLOW_CHILD
+
+static volatile int usr1_got;
+
+static void handler_usr1 (int signo)
+{
+  usr1_got++;
+}
+
+static void forkoff (int nr)
+{
+  pid_t child;
+  int i, loop;
+  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++;
+      breakpoint ();
+
+#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 (loop = 0; loop < 1000; loop++)
+        {
+	  /* Parent either died (and USR1_GOT is zero) or it succeeded.  */
+	  if (kill (getppid (), 0) != 0)
+	    break;
+	  /* Parent succeeded?  */
+	  if (usr1_got)
+	    break;
+
+	  delay ();
+	}
+      assert (usr1_got);
+
+      /* We must get caught here (against a false watchpoint removal).  */
+
+      breakpoint ();
+    }
+
+  i = sigaction (SIGUSR1, &oldact, NULL);
+  assert (i == 0);
+}
+
+#else
+# error "!FOLLOW_PARENT && !FOLLOW_CHILD"
+#endif
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/watchpoint-fork-mt.c	3 Jan 2008 16:17:35 -0000
@@ -0,0 +1,154 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+   Copyright 2008
+   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)
+
+/* Non-atomic `var++' should not hurt as we synchronize the threads by the STEP
+   variable.  Hit-comments need to be duplicite there to catch both at-stops
+   and behind-stops, depending on the target.  */
+
+static volatile int var;
+
+static void dummy (void)
+{
+}
+
+static void breakpoint (void)
+{
+}
+
+/* Include here the functions:
+   static void forkoff (int nr);
+   static void delay (void);  */
+
+static pthread_t thread;
+static volatile int step;
+#define THREAD
+
+#include "watchpoint-fork-forkoff.c"
+
+static void *start (void *arg)
+{
+  if (step >= 3)
+    goto step_3;
+
+  while (step != 1)
+    delay ();
+
+  var++;	/* validity-thread-B */
+  dummy ();	/* validity-thread-B */
+  step = 2;
+  while (step != 3)
+    {
+      if (step == 99)
+        goto step_99;
+      delay ();
+    }
+
+step_3:
+  if (step >= 5)
+    goto step_5;
+
+  var++;	/* after-fork1-B */
+  dummy ();	/* after-fork1-B */
+  step = 4;
+  while (step != 5)
+    {
+      if (step == 99)
+        goto step_99;
+      delay ();
+    }
+
+step_5:
+  var++;	/* after-fork2-B */
+  dummy ();	/* after-fork2-B */
+  return (void *) 5UL;
+
+step_99:
+  /* We must not get caught here (against a forgotten breakpoint).  */
+  var++;
+  breakpoint ();
+  return (void *) 99UL;
+}
+
+int main (void)
+{ 
+  int i;
+  void *thread_result;
+
+  setbuf (stdout, NULL);
+  printf ("main: %d\n", (int) gettid ());
+
+  /* General watchpoints validity.  */
+  var++;	/* validity-first */
+  dummy ();	/* validity-first */
+
+  i = pthread_create (&thread, NULL, start, NULL);
+  assert (i == 0);
+
+  var++;	/* validity-thread-A */
+  dummy ();	/* validity-thread-A */
+  step = 1;
+  while (step != 2)
+    delay ();
+
+  /* Hardware watchpoints got disarmed here.  */
+  forkoff (1);
+
+  var++;	/* after-fork1-A */
+  dummy ();	/* 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)
+    delay ();
+
+  /* A sanity check for double hardware watchpoints removal.  */
+  forkoff (2);
+
+  var++;	/* after-fork2-A */
+  dummy ();	/* 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);
+
+  return 0;
+}
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/watchpoint-fork.c	3 Jan 2008 16:17:35 -0000
@@ -0,0 +1,56 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+   Copyright 2008
+   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>
+
+static volatile int var;
+
+static void breakpoint (void)
+{
+}
+
+/* Include here the function:
+   static void forkoff (int nr);  */
+
+#include "watchpoint-fork-forkoff.c"
+
+int main (void)
+{
+  setbuf (stdout, NULL);
+  printf ("main: %d\n", (int) getpid ());
+
+  /* General watchpoints validity.  */
+  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++;
+
+  return 0;
+}
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/watchpoint-fork.exp	3 Jan 2008 16:17:35 -0000
@@ -0,0 +1,140 @@
+# Copyright 2008 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.
+
+proc test {type symbol} {
+    global objdir subdir srcdir
+
+    global pf_prefix
+    set prefix_test $pf_prefix
+    lappend pf_prefix "$type:"
+    set prefix_mt $pf_prefix
+
+    # no threads
+
+    set pf_prefix $prefix_mt
+    lappend pf_prefix "singlethreaded:"
+
+    set testfile watchpoint-fork
+    set srcfile ${testfile}.c
+    set binfile ${objdir}/${subdir}/${testfile}
+
+    if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable "debug additional_flags=-D$symbol"] != "" } {
+	untested "Couldn't compile test program"
+	return -1
+    }
+
+    gdb_exit
+    gdb_start
+    gdb_reinitialize_dir $srcdir/$subdir
+    gdb_load ${binfile}
+
+    gdb_test "set follow-fork-mode $type"
+    # Testcase uses it for the `follow-fork-mode child' type.
+    gdb_test "handle SIGUSR1 nostop noprint pass"
+
+    if { ![runto_main] } then {
+	gdb_suppress_tests
+	return
+    }
+
+    # Install the watchpoint only after getting into MAIN - workaround some PPC
+    # problem.
+    gdb_test "watch var" "atchpoint 2: var" "Set the watchpoint"
+
+    # It is never hit but it should not be left over in the fork()ed-off child.
+    gdb_breakpoint "breakpoint"
+
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 0.*New value = 1.*forkoff *\\(1\\).*" "watchpoints work"
+    gdb_test "continue" \
+	     "reakpoint 3, breakpoint.*" "breakpoint after the first fork"
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 1.*New value = 2.*forkoff *\\(2\\).*" "watchpoint after the first fork"
+    gdb_test "continue" \
+	     "reakpoint 3, breakpoint.*" "breakpoint after the second fork"
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 2.*New value = 3.*return *0;" "watchpoint after the second fork"
+    gdb_test "continue" "Continuing..*Program exited normally." "finish"
+
+
+    # threads
+
+    set pf_prefix $prefix_mt
+    lappend pf_prefix "multithreaded:"
+
+    set testfile watchpoint-fork-mt
+    set srcfile ${testfile}.c
+    set binfile ${objdir}/${subdir}/${testfile}
+
+    if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable "debug additional_flags=-D$symbol"] != "" } {
+	untested "Couldn't compile test program"
+	return -1
+    }
+
+    gdb_exit
+    gdb_start
+    gdb_reinitialize_dir $srcdir/$subdir
+    gdb_load ${binfile}
+
+    gdb_test "set follow-fork-mode $type"
+    # Testcase uses it for the `follow-fork-mode child' type.
+    gdb_test "handle SIGUSR1 nostop noprint pass"
+
+    if { ![runto_main] } then {
+	gdb_suppress_tests
+	return
+    }
+
+    # Install the watchpoint only after getting into MAIN - workaround some PPC
+    # problem.
+    gdb_test "watch var" "atchpoint 2: var" "Set the watchpoint"
+
+    # It is never hit but it should not be left over in the fork()ed-off child.
+    gdb_breakpoint "breakpoint"
+
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 0.*New value = 1.*validity-first.*" "singlethread watchpoints work"
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 1.*New value = 2.*validity-thread-A.*" "multithreaded watchpoints work at A"
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 2.*New value = 3.*validity-thread-B.*" "multithreaded watchpoints work at B"
+    gdb_test "continue" \
+	     "reakpoint 3, breakpoint.*" "breakpoint (A) after the first fork"
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 3.*New value = 4.*after-fork1-A.*" "watchpoint A after the first fork"
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 4.*New value = 5.*after-fork1-B.*" "watchpoint B after the first fork"
+    gdb_test "continue" \
+	     "reakpoint 3, breakpoint.*" "breakpoint (A) after the second fork"
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 5.*New value = 6.*after-fork2-A.*" "watchpoint A after the second fork"
+    gdb_test "continue" \
+	     "atchpoint 2: var.*Old value = 6.*New value = 7.*after-fork2-B.*" "watchpoint B after the second fork"
+    gdb_test "continue" "Continuing..*Program exited normally." "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
+}


More information about the Gdb-patches mailing list