]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Software watchpoints and threaded inferiors (WIP, submit independently)
authorPedro Alves <pedro@palves.net>
Fri, 23 Jun 2023 14:31:38 +0000 (15:31 +0100)
committerPedro Alves <pedro@palves.net>
Mon, 9 Jun 2025 17:49:19 +0000 (18:49 +0100)
Need to convince myself of the approach.

Change-Id: If649d30233ac1c2e57ea09cc8050e35f7a4bcf12

gdb/gdbthread.h
gdb/infrun.c
gdb/testsuite/gdb.base/watchpoint.c
gdb/testsuite/gdb.base/watchpoint.exp

index 344c808e9cb69506ad449e991ab1500931e23a67..2ea0fac2b9808b376c1ede0a771fd1b1495a3c36 100644 (file)
@@ -192,6 +192,16 @@ struct thread_control_state
      call.  */
   int in_infcall = 0;
 
+  /* True if this thread is single stepping due to a software
+     watchpoint.  This is set on the selected thread when an execution
+     command is issued.  It is not set on other threads that happen to
+     run because schedlock disabled.  This is so all-stop targets and
+     all-stop-on-top-of-non-stop targets behave the same as GDB
+     historically has behaved with all-stop backends -- with a
+     software watchpoint, only the selected/leader thread is single
+     stepped.  */
+  bool stepped_for_sw_watch = false;
+
   enum step_over_calls_kind step_over_calls = STEP_OVER_NONE;
 
   /* Nonzero if stopped due to a step command.  */
index 64ded81cf119caff37ee25ec9197ba12436811c1..8ce7245fbce9bfd092da4f3ff6460a1b1ab0a784 100644 (file)
@@ -2828,7 +2828,7 @@ resume_1 (enum gdb_signal sig)
 
   /* If we have a breakpoint to step over, make sure to do a single
      step only.  Same if we have software watchpoints.  */
-  if (tp->control.trap_expected || bpstat_should_step ())
+  if (tp->control.trap_expected || tp->control.stepped_for_sw_watch)
     tp->control.may_range_step = 0;
 
   /* If displaced stepping is enabled, step over breakpoints by executing a
@@ -3116,6 +3116,7 @@ clear_proceed_status_thread (struct thread_info *tp)
   tp->control.step_stack_frame_id = null_frame_id;
   tp->control.step_over_calls = STEP_OVER_UNDEBUGGABLE;
   tp->control.step_start_function = nullptr;
+  tp->control.stepped_for_sw_watch = false;
   tp->stop_requested = false;
 
   tp->control.stop_step = 0;
@@ -3730,6 +3731,11 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
      Ctrl-C from within target_pass_ctrlc).  */
   target_terminal::inferior ();
 
+  /* Set the selected thread single stepping if there's a software
+     watchpoint.  Do this before GDB decides to switch to another
+     thread to step it past a breakpoint.  */
+  cur_thr->control.stepped_for_sw_watch = bpstat_should_step ();
+
   /* In a multi-threaded task we may select another thread and
      then continue or step.
 
@@ -8744,7 +8750,7 @@ should_step (thread_info *tp)
           && tp->control.step_resume_breakpoint == nullptr)
          || tp->control.trap_expected
          || tp->stepped_breakpoint
-         || bpstat_should_step ());
+         || tp->control.stepped_for_sw_watch);
 }
 
 /* Inferior has stepped into a subroutine call with source code that
index 3d6e36ff0815e2cbd27ee69cef6f8a21301a650f..3043bf3783c9f628d8d9b00511442447c976043e 100644 (file)
@@ -169,7 +169,7 @@ func7 (void)
   foo4.val[3] = 33;
 }
 
-int main ()
+int test_main ()
 {
   struct1.val = 1;
   struct2.val = 2;
@@ -256,3 +256,54 @@ int main ()
 
   return 0;
 }
+
+#if USE_THREADS
+#include <pthread.h>
+
+#define NUM 5
+
+/* Barrier used to wait until all threads are started, before calling
+   test_main.  */
+static pthread_barrier_t threads_started_barrier;
+
+/* Barrier used to prevent threads from exiting until test_main
+   returns.  */
+static pthread_barrier_t threads_exit_barrier;
+
+static void *
+thread_function (void *arg)
+{
+  pthread_barrier_wait (&threads_started_barrier);
+  pthread_barrier_wait (&threads_exit_barrier);
+}
+
+#endif /* USE_THREADS */
+
+int
+main ()
+{
+  int res;
+#if USE_THREADS
+  pthread_t threads[NUM];
+  long i;
+
+  pthread_barrier_init (&threads_started_barrier, NULL, NUM + 1);
+  pthread_barrier_init (&threads_exit_barrier, NULL, NUM + 1);
+
+  for (i = 0; i < NUM; i++)
+    pthread_create (&threads[i], NULL, thread_function, NULL);
+
+  pthread_barrier_wait (&threads_started_barrier);
+#endif /* USE_THREADS */
+
+  res = test_main ();
+
+#if USE_THREADS
+  pthread_barrier_wait (&threads_exit_barrier);
+
+  for (i = 0; i < NUM; i++)
+    pthread_join (threads[i], NULL);
+#endif /* USE_THREADS */
+
+  return res;
+}
index fba8ac6ed507edb50b78c97381113047f684ff85..d0741bc129703939cb40b418379524ab962f1440 100644 (file)
@@ -25,9 +25,15 @@ set allow_hw_watchpoint_tests_p [allow_hw_watchpoint_tests]
 
 standard_testfile
 
-if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
-     untested "failed to compile"
-     return -1
+if {[build_executable "failed to prepare" $testfile $srcfile {debug}]} {
+    return -1
+}
+
+if {[build_executable "failed to prepare" $testfile-threads $srcfile \
+        {debug pthreads additional_flags=-DUSE_THREADS=1}]} {
+    set use_threads 0
+} else {
+    set use_threads 1
 }
 
 # True if we're forcing no hardware watchpoints.
@@ -197,7 +203,7 @@ Continuing.*\[Ww\]atchpoint.*ival3.*Old value = -1.*New value = 0.*ival3 = count
 
     if [target_info exists gdb,noresults] { return }
 
-    gdb_continue_to_end "continue to exit in test_simple_watchpoint"
+    gdb_continue_to_end "continue to exit in test_simple_watchpoint" continue 1
 }
 
 # Test disabling watchpoints.
@@ -268,7 +274,7 @@ proc test_disabling_watchpoints {} {
     
     if [target_info exists gdb,noresults] { return }
 
-    gdb_continue_to_end "continue to exit in test_disabling_watchpoints"
+    gdb_continue_to_end "continue to exit in test_disabling_watchpoints" continue 1
 }
 
 # Test stepping and other mundane operations with watchpoints enabled
@@ -434,7 +440,7 @@ proc test_watchpoint_triggered_in_syscall {} {
 
        if [target_info exists gdb,noresults] { return }
 
-       gdb_continue_to_end "continue to exit in test_watchpoint_triggered_in_syscall"
+       gdb_continue_to_end "continue to exit in test_watchpoint_triggered_in_syscall" continue 1
     }
 }
 
@@ -569,7 +575,7 @@ proc test_complex_watchpoint {} {
 
        if [target_info exists gdb,noresults] { return }
 
-       gdb_continue_to_end "continue to exit in test_complex_watchpoint"
+       gdb_continue_to_end "continue to exit in test_complex_watchpoint" continue 1
     }
 }
 
@@ -833,7 +839,7 @@ proc test_no_hw_watchpoints {} {
     # (This proves rather little on kernels that don't support
     # fast watchpoints, but still...)
     #
-    if {![runto_main]} {
+    if {![runto test_main]} {
        return
     }
 
@@ -1018,6 +1024,35 @@ if {$allow_hw_watchpoint_tests_p} {
     }
 }
 
+# Test software watchpoints when the program is multithreaded.  On
+# some systems, the runtime always spawns some helper threads.
+# Testing on one such system -- Cygwin -- exposed paths in the
+# software single-step implementation that weren't exercised anywhere
+# else.  We thus here test the basic software watchpoint support with
+# explicitly started extra threads, to exercise it on all systems
+# (that can do threads).
+
+proc do_threaded_sw_tests {} {
+    global testfile
+    global no_hw
+    global allow_hw_watchpoint_tests_p
+
+    clean_restart $testfile-threads
+
+    gdb_test_no_output "set can-use-hw-watchpoints 0"\
+       "disable fast watches"
+
+    if {[initialize]} {
+       test_simple_watchpoint
+    }
+}
+
+if {$use_threads} {
+    with_test_prefix "threaded-no-hw" {
+       do_threaded_sw_tests
+    }
+}
+
 # Restore old timeout
 set timeout $prev_timeout
 verbose "Timeout now $timeout sec.\n"