This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[patch 4/4] Fix hw watchpoints: across fork()
- From: Jan Kratochvil <jan dot kratochvil at redhat dot com>
- To: gdb-patches at sourceware dot org
- Date: Mon, 17 Aug 2009 21:46:37 +0200
- Subject: [patch 4/4] Fix hw watchpoints: across fork()
Hi,
watchpoints broke in wrong ways when inferior fork()ed. Patch fixes both
"follow-fork-mode parent" (default) and "follow-fork-mode child" for both
singlethreaded/multithreaded modes. It does not try to fix
"set detach-on-fork off" which was broken for multithreading and I did not
checking this patch wrt multi-exec (nor non-stop).
"child: multithreaded: *" testcases require glibc sourceware PR 5983 fix.
Not XFAILed as both the fix is now pretty old and such XFAIL could possibly
falsely mask some future GDB regression.
Whether the "i386_dr_low.detach" hook should exist may depend on more cleanups:
Moving i386-nat.c watchpoints reference counter ("dr_ref_count") to a non-arch
generic code. Changing custom suboptimal cache "amd64_linux_dr" maybe to
a "struct regcache" with optimal fetching/flushing wrt inferior stops.
This code is based on 2007 Fedora gdb-6.6-multifork-debugreg.patch.
Thanks,
Jan
gdb/
2009-08-17 Jan Kratochvil <jan.kratochvil@redhat.com>
Fix watchpoints across various kinds of inferior forks.
* amd64-linux-nat.c (amd64_linux_dr_detach): New.
(_initialize_amd64_linux_nat): Install it.
* breakpoint.c (detach_breakpoints): Call target_detach_watchpoints.
(remove_breakpoint <bp_loc_hardware_watchpoint>): New comment. New
conditional on mark_uninserted. Simplify setting b->inserted.
(bpstat_check_watchpoint): New return value. Comment it.
(bpstat_stop_status): New variable bs_prev. Free current BS for
bpstat_check_watchpoint returning 0.
* i386-linux-nat.c (i386_linux_dr_detach): New.
(_initialize_i386_linux_nat): Install it.
* i386-nat.c (i386_detach_watchpoints): New.
(i386_use_watchpoints): Install it.
* i386-nat.h (struct i386_dr_low_type): New field detach. Comment it.
* infrun.c (handle_inferior_event <TARGET_WAITKIND_FORKED>)
(handle_inferior_event <TARGET_WAITKIND_VFORKED>)
(handle_inferior_event <TARGET_WAITKIND_EXECD>): Call
watchpoints_triggered.
* linux-nat.c (resume_callback <stopped && status == 0>)
(linux_nat_resume): Reset WATCHPOINT_HIT_SET.
(linux_nat_detach_watchpoints): New.
(linux_nat_add_target): Install it.
* ppc-linux-nat.c (ppc_linux_detach_watchpoints): New.
(_initialize_ppc_linux_nat): Install it.
* target.c (debug_to_detach_watchpoints): New.
(update_current_target): Call INHERIT and de_fault for it.
(setup_target_debug): Install it.
* target.h (struct target_ops <to_detach_watchpoints>): New.
(target_detach_watchpoints): New macro.
gdb/testsuite/
2009-08-17 Jan Kratochvil <jan.kratochvil@redhat.com>
* gdb.threads/watchpoint-fork-forkoff.c, gdb.threads/watchpoint-fork.c,
gdb.threads/watchpoint-fork-mt.c gdb.threads/watchpoint-fork.exp: New.
--- a/gdb/amd64-linux-nat.c
+++ b/gdb/amd64-linux-nat.c
@@ -350,6 +350,20 @@ amd64_linux_dr_unset_status (unsigned long mask)
}
}
+/* See i386_dr_low_type.detach. Do not use wrappers amd64_linux_dr_set_control
+ or amd64_linux_dr_reset_addr as they would modify the register cache
+ (amd64_linux_dr). */
+
+static void
+amd64_linux_dr_detach (void)
+{
+ int regnum;
+
+ amd64_linux_dr_set (inferior_ptid, DR_CONTROL, 0);
+ amd64_linux_dr_unset_status (~0UL);
+ for (regnum = DR_FIRSTADDR; regnum <= DR_LASTADDR; regnum++)
+ amd64_linux_dr_set (inferior_ptid, regnum, 0);
+}
static void
amd64_linux_new_thread (ptid_t ptid)
@@ -702,6 +716,7 @@ _initialize_amd64_linux_nat (void)
i386_dr_low.reset_addr = amd64_linux_dr_reset_addr;
i386_dr_low.get_status = amd64_linux_dr_get_status;
i386_dr_low.unset_status = amd64_linux_dr_unset_status;
+ i386_dr_low.detach = amd64_linux_dr_detach;
i386_set_debug_register_length (8);
/* Override the GNU/Linux inferior startup hook. */
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -1690,6 +1690,7 @@ detach_breakpoints (int pid)
if (b->inserted)
val |= remove_breakpoint (b, mark_inserted);
}
+ val |= target_detach_watchpoints ();
do_cleanups (old_chain);
return val;
}
@@ -1782,12 +1783,14 @@ remove_breakpoint (struct bp_location *b, insertion_state_t is)
return val;
b->inserted = (is == mark_inserted);
}
- else if (b->loc_type == bp_loc_hardware_watchpoint)
+ /* bp_loc_hardware_watchpoint with mark_inserted is being handled by
+ target_detach_watchpoints. */
+ else if (b->loc_type == bp_loc_hardware_watchpoint && is == mark_uninserted)
{
struct value *v;
struct value *n;
- b->inserted = (is == mark_inserted);
+ b->inserted = 0;
val = target_remove_watchpoint (b->address, b->length,
b->watchpoint_type);
@@ -2961,8 +2964,12 @@ bpstat_check_location (const struct bp_location *bl, CORE_ADDR bp_addr)
/* If BS refers to a watchpoint, determine if the watched values
has actually changed, and we should stop. If not, set BS->stop
- to 0. */
-static void
+ to 0.
+ Return 0 for watchpoints which could not be the cause of this trap.
+ In such case PRINT_IT will be print_it_noop and STOP will be 0.
+ Otherwise return 1 but in such case it is not guaranteed whether this
+ breakpoint did or did not trigger this trap. */
+static int
bpstat_check_watchpoint (bpstat bs)
{
const struct bp_location *bl = bs->breakpoint_at;
@@ -3051,8 +3058,10 @@ bpstat_check_watchpoint (bpstat bs)
anything for this watchpoint. */
bs->print_it = print_it_noop;
bs->stop = 0;
+ return 0;
}
}
+ return 1;
}
@@ -3156,6 +3165,8 @@ bpstat_stop_status (CORE_ADDR bp_addr, ptid_t ptid)
ALL_BP_LOCATIONS (bl)
{
+ bpstat bs_prev = bs;
+
b = bl->owner;
gdb_assert (b);
if (!breakpoint_enabled (b) && b->enable_state != bp_permanent)
@@ -3176,6 +3187,7 @@ bpstat_stop_status (CORE_ADDR bp_addr, ptid_t ptid)
/* Come here if it's a watchpoint, or if the break address matches */
bs = bpstat_alloc (bl, bs); /* Alloc a bpstat to explain stop */
+ gdb_assert (bs_prev->next == bs);
/* Assume we stop. Should we find watchpoint that is not actually
triggered, or if condition of breakpoint is false, we'll reset
@@ -3183,9 +3195,18 @@ bpstat_stop_status (CORE_ADDR bp_addr, ptid_t ptid)
bs->stop = 1;
bs->print = 1;
- bpstat_check_watchpoint (bs);
- if (!bs->stop)
- continue;
+ if (!bpstat_check_watchpoint (bs))
+ {
+ /* Ensure bpstat_explains_signal stays false if this BL could not be
+ the cause of this trap. */
+
+ gdb_assert (bs->print_it == print_it_noop);
+ gdb_assert (!bs->stop);
+ xfree (bs);
+ bs = bs_prev;
+ bs->next = NULL;
+ continue;
+ }
if (b->type == bp_thread_event || b->type == bp_overlay_event
|| b->type == bp_longjmp_master)
--- a/gdb/i386-linux-nat.c
+++ b/gdb/i386-linux-nat.c
@@ -696,6 +696,21 @@ i386_linux_dr_unset_status (unsigned long mask)
}
}
+/* See i386_dr_low_type.detach. Do not use wrappers i386_linux_dr_set_control
+ or i386_linux_dr_reset_addr as they would modify the register cache
+ (i386_linux_dr). */
+
+static void
+i386_linux_dr_detach (void)
+{
+ int regnum;
+
+ i386_linux_dr_set (inferior_ptid, DR_CONTROL, 0);
+ i386_linux_dr_unset_status (~0UL);
+ for (regnum = DR_FIRSTADDR; regnum <= DR_LASTADDR; regnum++)
+ i386_linux_dr_set (inferior_ptid, regnum, 0);
+}
+
static void
i386_linux_new_thread (ptid_t ptid)
{
@@ -863,6 +878,7 @@ _initialize_i386_linux_nat (void)
i386_dr_low.reset_addr = i386_linux_dr_reset_addr;
i386_dr_low.get_status = i386_linux_dr_get_status;
i386_dr_low.unset_status = i386_linux_dr_unset_status;
+ i386_dr_low.detach = i386_linux_dr_detach;
i386_set_debug_register_length (4);
/* Override the default ptrace resume method. */
--- a/gdb/i386-nat.c
+++ b/gdb/i386-nat.c
@@ -532,6 +532,17 @@ i386_remove_watchpoint (CORE_ADDR addr, int len, int type)
return retval;
}
+/* See target_detach_watchpoints. */
+
+static int
+i386_detach_watchpoints (void)
+{
+ if (i386_dr_low.detach)
+ i386_dr_low.detach ();
+
+ return 0;
+}
+
/* Return non-zero if we can watch a memory region that starts at
address ADDR and whose length is LEN bytes. */
@@ -681,6 +692,7 @@ i386_use_watchpoints (struct target_ops *t)
t->to_stopped_data_address = i386_stopped_data_address;
t->to_insert_watchpoint = i386_insert_watchpoint;
t->to_remove_watchpoint = i386_remove_watchpoint;
+ t->to_detach_watchpoints = i386_detach_watchpoints;
t->to_insert_hw_breakpoint = i386_insert_hw_breakpoint;
t->to_remove_hw_breakpoint = i386_remove_hw_breakpoint;
}
--- a/gdb/i386-nat.h
+++ b/gdb/i386-nat.h
@@ -63,6 +63,10 @@ extern void i386_use_watchpoints (struct target_ops *);
unset_status -- unset the specified bits of the debug
status (DR6) register for all LWPs
+ detach -- clear all debug registers of only the
+ INFERIOR_PTID task without affecting any
+ register caches.
+
Additionally, the native file should set the debug_register_length
field to 4 or 8 depending on the number of bytes used for
deubg registers. */
@@ -74,6 +78,7 @@ struct i386_dr_low_type
void (*reset_addr) (int);
unsigned long (*get_status) (void);
void (*unset_status) (unsigned long);
+ void (*detach) (void);
int debug_register_length;
};
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2630,6 +2630,10 @@ handle_inferior_event (struct execution_control_state *ecs)
stop_pc = regcache_read_pc (get_thread_regcache (ecs->ptid));
+ /* Clear WATCHPOINT_TRIGGERED values from previous stop which could
+ confuse bpstat_stop_status and bpstat_explains_signal. */
+ watchpoints_triggered (&ecs->ws);
+
ecs->event_thread->stop_bpstat = bpstat_stop_status (stop_pc, ecs->ptid);
ecs->random_signal = !bpstat_explains_signal (ecs->event_thread->stop_bpstat);
@@ -2667,6 +2671,10 @@ handle_inferior_event (struct execution_control_state *ecs)
stop_pc = regcache_read_pc (get_thread_regcache (ecs->ptid));
+ /* Clear WATCHPOINT_TRIGGERED values from previous stop which could
+ confuse bpstat_stop_status and bpstat_explains_signal. */
+ watchpoints_triggered (&ecs->ws);
+
/* This causes the eventpoints and symbol table to be reset.
Must do this now, before trying to determine whether to
stop. */
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1629,6 +1629,7 @@ resume_callback (struct lwp_info *lp, void *data)
lp->stopped = 0;
lp->step = 0;
memset (&lp->siginfo, 0, sizeof (lp->siginfo));
+ lp->watchpoint_hit_set = 0;
}
else if (lp->stopped && debug_linux_nat)
fprintf_unfiltered (gdb_stdlog, "RC: Not resuming sibling %s (has pending)\n",
@@ -1766,6 +1767,7 @@ linux_nat_resume (struct target_ops *ops,
linux_ops->to_resume (linux_ops, ptid, step, signo);
memset (&lp->siginfo, 0, sizeof (lp->siginfo));
+ lp->watchpoint_hit_set = 0;
if (debug_linux_nat)
fprintf_unfiltered (gdb_stdlog,
@@ -2203,6 +2205,39 @@ linux_nat_stopped_data_address (struct target_ops *ops, CORE_ADDR *addr_p)
return lp->watchpoint_hit_set;
}
+/* In `set follow-fork-mode child' with multithreaded parent we need to detach
+ watchpoints from all the LWPs. In such case INFERIOR_PTID will be the
+ non-threaded new child while LWP_LIST will still contain all the threads of
+ the parent being detached. */
+
+static int
+linux_nat_detach_watchpoints (void)
+{
+ struct lwp_info *lp;
+ int found = 0, retval = 0;
+ ptid_t filter = pid_to_ptid (ptid_get_pid (inferior_ptid));
+ struct cleanup *old_chain = save_inferior_ptid ();
+
+ for (lp = lwp_list; lp; lp = lp->next)
+ if (ptid_match (lp->ptid, filter))
+ {
+ inferior_ptid = lp->ptid;
+ retval |= linux_ops->to_detach_watchpoints ();
+ found = 1;
+ }
+
+ do_cleanups (old_chain);
+
+ if (!found)
+ {
+ gdb_assert (!is_lwp (inferior_ptid));
+
+ retval |= linux_ops->to_detach_watchpoints ();
+ }
+
+ return retval;
+}
+
/* Wait until LP is stopped. */
static int
@@ -4918,6 +4953,8 @@ linux_nat_add_target (struct target_ops *t)
t->to_has_thread_control = tc_schedlock;
if (linux_ops->to_stopped_data_address)
t->to_stopped_data_address = linux_nat_stopped_data_address;
+ if (linux_ops->to_detach_watchpoints)
+ t->to_detach_watchpoints = linux_nat_detach_watchpoints;
t->to_can_async_p = linux_nat_can_async_p;
t->to_is_async_p = linux_nat_is_async_p;
--- a/gdb/ppc-linux-nat.c
+++ b/gdb/ppc-linux-nat.c
@@ -1391,6 +1391,24 @@ ppc_linux_remove_watchpoint (CORE_ADDR addr, int len, int rw)
return 0;
}
+/* See target_detach_watchpoints. Do not use wrapper
+ ppc_linux_remove_watchpoint as it would modify the register cache
+ (saved_dabr_value). */
+
+static int
+ppc_linux_detach_watchpoints (void)
+{
+ pid_t tid;
+
+ tid = TIDGET (inferior_ptid);
+ if (tid == 0)
+ tid = PIDGET (inferior_ptid);
+
+ if (ptrace (PTRACE_SET_DEBUGREG, tid, NULL, NULL) < 0)
+ return -1;
+ return 0;
+}
+
static void
ppc_linux_new_thread (ptid_t ptid)
{
@@ -1648,6 +1666,7 @@ _initialize_ppc_linux_nat (void)
t->to_region_ok_for_hw_watchpoint = ppc_linux_region_ok_for_hw_watchpoint;
t->to_insert_watchpoint = ppc_linux_insert_watchpoint;
t->to_remove_watchpoint = ppc_linux_remove_watchpoint;
+ t->to_detach_watchpoints = ppc_linux_detach_watchpoints;
t->to_stopped_by_watchpoint = ppc_linux_stopped_by_watchpoint;
t->to_stopped_data_address = ppc_linux_stopped_data_address;
t->to_watchpoint_addr_within_range = ppc_linux_watchpoint_addr_within_range;
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -124,6 +124,8 @@ static int debug_to_insert_watchpoint (CORE_ADDR, int, int);
static int debug_to_remove_watchpoint (CORE_ADDR, int, int);
+static int debug_to_detach_watchpoints (void);
+
static int debug_to_stopped_by_watchpoint (void);
static int debug_to_stopped_data_address (struct target_ops *, CORE_ADDR *);
@@ -584,6 +586,7 @@ update_current_target (void)
INHERIT (to_remove_hw_breakpoint, t);
INHERIT (to_insert_watchpoint, t);
INHERIT (to_remove_watchpoint, t);
+ INHERIT (to_detach_watchpoints, t);
INHERIT (to_stopped_data_address, t);
INHERIT (to_have_steppable_watchpoint, t);
INHERIT (to_have_continuable_watchpoint, t);
@@ -694,6 +697,9 @@ update_current_target (void)
de_fault (to_remove_watchpoint,
(int (*) (CORE_ADDR, int, int))
return_minus_one);
+ de_fault (to_detach_watchpoints,
+ (int (*) (void))
+ return_zero);
de_fault (to_stopped_by_watchpoint,
(int (*) (void))
return_zero);
@@ -3095,6 +3101,19 @@ debug_to_remove_watchpoint (CORE_ADDR addr, int len, int type)
return retval;
}
+static int
+debug_to_detach_watchpoints (void)
+{
+ int retval;
+
+ retval = debug_target.to_detach_watchpoints ();
+
+ fprintf_unfiltered (gdb_stdlog,
+ "target_detach_watchpoints () = %ld\n",
+ (unsigned long) retval);
+ return retval;
+}
+
static void
debug_to_terminal_init (void)
{
@@ -3342,6 +3361,7 @@ setup_target_debug (void)
current_target.to_remove_hw_breakpoint = debug_to_remove_hw_breakpoint;
current_target.to_insert_watchpoint = debug_to_insert_watchpoint;
current_target.to_remove_watchpoint = debug_to_remove_watchpoint;
+ current_target.to_detach_watchpoints = debug_to_detach_watchpoints;
current_target.to_stopped_by_watchpoint = debug_to_stopped_by_watchpoint;
current_target.to_stopped_data_address = debug_to_stopped_data_address;
current_target.to_watchpoint_addr_within_range = debug_to_watchpoint_addr_within_range;
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -376,6 +376,7 @@ struct target_ops
int (*to_remove_hw_breakpoint) (struct gdbarch *, struct bp_target_info *);
int (*to_remove_watchpoint) (CORE_ADDR, int, int);
int (*to_insert_watchpoint) (CORE_ADDR, int, int);
+ int (*to_detach_watchpoints) (void);
int (*to_stopped_by_watchpoint) (void);
int to_have_steppable_watchpoint;
int to_have_continuable_watchpoint;
@@ -1120,6 +1121,15 @@ extern char *normal_pid_to_str (ptid_t ptid);
#define target_remove_watchpoint(addr, len, type) \
(*current_target.to_remove_watchpoint) (addr, len, type)
+/* Clear all debug registers without affecting any register caches. Function
+ acts on INFERIOR_PTID which should be the forked-off process, either the
+ non-threaded child one or the threaded parent one, depending on `set
+ follow-fork-mode'. Both watchpoints and hardware breakpoints get removed.
+ Return 0 on success, -1 on failure. */
+
+#define target_detach_watchpoints() \
+ (*current_target.to_detach_watchpoints) ()
+
#define target_insert_hw_breakpoint(gdbarch, bp_tgt) \
(*current_target.to_insert_hw_breakpoint) (gdbarch, bp_tgt)
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/watchpoint-fork-forkoff.c
@@ -0,0 +1,175 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+ Copyright 2008, 2009 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 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, 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, breakpoint () 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++;
+ breakpoint ();
+
+ _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). */
+ 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
+++ b/gdb/testsuite/gdb.threads/watchpoint-fork-mt.c
@@ -0,0 +1,157 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+ Copyright 2008, 2009 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 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
+++ b/gdb/testsuite/gdb.threads/watchpoint-fork.c
@@ -0,0 +1,57 @@
+/* Test case for forgotten hw-watchpoints after fork()-off of a process.
+
+ Copyright 2008, 2009 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 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
+++ b/gdb/testsuite/gdb.threads/watchpoint-fork.exp
@@ -0,0 +1,130 @@
+# Copyright 2008, 2009 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
+
+ set test watchpoint-fork
+
+ 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 executable ${test}-${type}
+ if { [gdb_compile ${srcdir}/${subdir}/${test}.c ${objdir}/${subdir}/${executable} executable [list debug additional_flags=-D$symbol]] != "" } {
+ untested ${test}.exp
+ return -1
+ }
+ clean_restart $executable
+
+ gdb_test "show detach-on-fork" "Whether gdb will detach the child of a fork is on."
+ gdb_test "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"
+
+ 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 executable ${test}-mt-${type}
+ if { [gdb_compile_pthreads ${srcdir}/${subdir}/${test}-mt.c ${objdir}/${subdir}/${executable} executable [list debug additional_flags=-D$symbol]] != "" } {
+ untested ${test}.exp
+ return -1
+ }
+ clean_restart $executable
+
+ 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
+}