From: Pedro Alves Date: Fri, 23 Jun 2023 14:31:38 +0000 (+0100) Subject: Software watchpoints and threaded inferiors (WIP, submit independently) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5bdfc71136dabfb9acf9c960894b8eacfae3c415;p=thirdparty%2Fbinutils-gdb.git Software watchpoints and threaded inferiors (WIP, submit independently) Need to convince myself of the approach. Change-Id: If649d30233ac1c2e57ea09cc8050e35f7a4bcf12 --- diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index 344c808e9cb..2ea0fac2b98 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -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. */ diff --git a/gdb/infrun.c b/gdb/infrun.c index 64ded81cf11..8ce7245fbce 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -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 diff --git a/gdb/testsuite/gdb.base/watchpoint.c b/gdb/testsuite/gdb.base/watchpoint.c index 3d6e36ff081..3043bf3783c 100644 --- a/gdb/testsuite/gdb.base/watchpoint.c +++ b/gdb/testsuite/gdb.base/watchpoint.c @@ -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 + +#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; +} diff --git a/gdb/testsuite/gdb.base/watchpoint.exp b/gdb/testsuite/gdb.base/watchpoint.exp index fba8ac6ed50..d0741bc1297 100644 --- a/gdb/testsuite/gdb.base/watchpoint.exp +++ b/gdb/testsuite/gdb.base/watchpoint.exp @@ -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"