| # Copyright 2022-2024 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/>. | 
 |  | 
 | # Some simple tests of inferior function calls from breakpoint | 
 | # conditions, in multi-threaded inferiors. | 
 | # | 
 | # This test sets up a multi-threaded inferior, and places a breakpoint | 
 | # at a location that many of the threads will reach.  We repeat the | 
 | # test with different conditions, sometimes a single thread should | 
 | # stop at the breakpoint, sometimes multiple threads should stop, and | 
 | # sometimes no threads should stop. | 
 |  | 
 | standard_testfile | 
 |  | 
 | if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \ | 
 | 	  {debug pthreads}] == -1 } { | 
 |     return | 
 | } | 
 |  | 
 | set cond_bp_line [gdb_get_line_number "Breakpoint here"] | 
 | set stop_bp_line [gdb_get_line_number "Stop marker"] | 
 | set nested_bp_line [gdb_get_line_number "Nested breakpoint"] | 
 | set segv_line [gdb_get_line_number "Segfault happens here"] | 
 |  | 
 | # Start GDB based on TARGET_ASYNC and TARGET_NON_STOP, and then runto main. | 
 | proc start_gdb_and_runto_main { target_async target_non_stop } { | 
 |     save_vars { ::GDBFLAGS } { | 
 | 	append ::GDBFLAGS \ | 
 | 	    " -ex \"maint set target-non-stop $target_non_stop\"" | 
 | 	append ::GDBFLAGS \ | 
 | 	    " -ex \"maintenance set target-async ${target_async}\"" | 
 |  | 
 | 	clean_restart ${::binfile} | 
 |     } | 
 |  | 
 |     if { ![runto_main] } { | 
 | 	return -1 | 
 |     } | 
 |  | 
 |     return 0 | 
 | } | 
 |  | 
 | # Run a test of GDB's conditional breakpoints, where the conditions include | 
 | # inferior function calls. | 
 | # | 
 | # CONDITION is the expression to be used as the breakpoint condition. | 
 | # | 
 | # N_EXPECTED_HITS is the number of threads that we expect to stop due to | 
 | # CONDITON. | 
 | # | 
 | # MESSAGE is used as a test name prefix. | 
 | proc run_condition_test { message n_expected_hits condition \ | 
 | 			      target_async target_non_stop } { | 
 |     with_test_prefix $message { | 
 |  | 
 | 	if { [start_gdb_and_runto_main $target_async \ | 
 | 		  $target_non_stop] == -1 } { | 
 | 	    return | 
 | 	} | 
 |  | 
 | 	# Use this convenience variable to track how often the | 
 | 	# breakpoint condition has been evaluated.  This should be | 
 | 	# once per thread. | 
 | 	gdb_test "set \$n_cond_eval = 0" | 
 |  | 
 | 	# Setup the conditional breakpoint. | 
 | 	gdb_breakpoint \ | 
 | 	    "${::srcfile}:${::cond_bp_line} if ((++\$n_cond_eval) && (${condition}))" | 
 |  | 
 | 	# And a breakpoint that we hit when the test is over, this one is | 
 | 	# not conditional.  Only the main thread gets here once all the | 
 | 	# other threads have finished. | 
 | 	gdb_breakpoint "${::srcfile}:${::stop_bp_line}" | 
 |  | 
 | 	# The number of times we stop at the conditional breakpoint. | 
 | 	set n_hit_condition 0 | 
 |  | 
 | 	# Now keep 'continue'-ing GDB until all the threads have finished | 
 | 	# and we reach the stop_marker breakpoint. | 
 | 	gdb_test_multiple "continue" "spot all breakpoint hits" { | 
 | 	    -re " worker_func \[^\r\n\]+${::srcfile}:${::cond_bp_line}\r\n${::decimal}\\s+\[^\r\n\]+Breakpoint here\[^\r\n\]+\r\n${::gdb_prompt} $" { | 
 | 		incr n_hit_condition | 
 | 		send_gdb "continue\n" | 
 | 		exp_continue | 
 | 	    } | 
 |  | 
 | 	    -re " stop_marker \[^\r\n\]+${::srcfile}:${::stop_bp_line}\r\n${::decimal}\\s+\[^\r\n\]+Stop marker\[^\r\n\]+\r\n${::gdb_prompt} $" { | 
 | 		pass $gdb_test_name | 
 | 	    } | 
 | 	} | 
 |  | 
 | 	gdb_assert { $n_hit_condition == $n_expected_hits } \ | 
 | 	    "stopped at breakpoint the expected number of times" | 
 |  | 
 | 	# Ensure the breakpoint condition was evaluated once per thread. | 
 | 	gdb_test "print \$n_cond_eval" "= 3" \ | 
 | 	    "condition was evaluated in each thread" | 
 |     } | 
 | } | 
 |  | 
 | # Check that after handling a conditional breakpoint (where the condition | 
 | # includes an inferior call), it is still possible to kill the running | 
 | # inferior, and then restart the inferior. | 
 | # | 
 | # At once point doing this would result in GDB giving an assertion error. | 
 | proc_with_prefix run_kill_and_restart_test { target_async target_non_stop } { | 
 |     # This test relies on the 'start' command, which is not possible with | 
 |     # the plain 'remote' target. | 
 |     if { [target_info gdb_protocol] == "remote" } { | 
 | 	return | 
 |     } | 
 |  | 
 |     if { [start_gdb_and_runto_main $target_async \ | 
 | 	      $target_non_stop] == -1 } { | 
 | 	return | 
 |     } | 
 |  | 
 |     # Setup the conditional breakpoint. | 
 |     gdb_breakpoint \ | 
 | 	"${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 1))" | 
 |     gdb_continue_to_breakpoint "worker_func" | 
 |  | 
 |     # Now kill the program being debugged. | 
 |     gdb_test "kill" "" "kill process" \ | 
 | 	"Kill the program being debugged.*y or n. $" "y" | 
 |  | 
 |     # Check we can restart the inferior.  At one point this would trigger an | 
 |     # assertion. | 
 |     gdb_start_cmd | 
 | } | 
 |  | 
 | # Create a conditional breakpoint which includes a call to a function that | 
 | # segfaults.  Run GDB and check what happens when the inferior segfaults | 
 | # during the inferior call. | 
 | proc_with_prefix run_bp_cond_segfaults { target_async target_non_stop } { | 
 |     if { [start_gdb_and_runto_main $target_async \ | 
 | 	      $target_non_stop] == -1 } { | 
 | 	return | 
 |     } | 
 |  | 
 |     # This test relies on the inferior segfaulting when trying to | 
 |     # access address zero. | 
 |     if { [is_address_zero_readable] } { | 
 | 	return | 
 |     } | 
 |  | 
 |     # Setup the conditional breakpoint, include a call to | 
 |     # 'function_that_segfaults', which triggers the segfault. | 
 |     gdb_breakpoint \ | 
 | 	"${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 0) && function_that_segfaults ())" | 
 |     set bp_1_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ | 
 | 		      "get number of conditional breakpoint"] | 
 |  | 
 |     gdb_test "continue" \ | 
 | 	[multi_line \ | 
 | 	     "Continuing\\." \ | 
 | 	     ".*" \ | 
 | 	     "Thread ${::decimal} \"infcall-from-bp\" received signal SIGSEGV, Segmentation fault\\." \ | 
 | 	     "${::hex} in function_that_segfaults \\(\\) at \[^\r\n\]+:${::segv_line}" \ | 
 | 	     "${::decimal}\\s+\[^\r\n\]+Segfault happens here\[^\r\n\]+" \ | 
 | 	     "Error in testing condition for breakpoint ${bp_1_num}:" \ | 
 | 	     "The program being debugged was signaled while in a function called from GDB\\." \ | 
 | 	     "GDB remains in the frame where the signal was received\\." \ | 
 | 	     "To change this behavior use \"set unwind-on-signal on\"\\." \ | 
 | 	     "Evaluation of the expression containing the function" \ | 
 | 	     "\\(function_that_segfaults\\) will be abandoned\\." \ | 
 | 	     "When the function is done executing, GDB will silently stop\\."] | 
 | } | 
 |  | 
 | # Create a conditional breakpoint which includes a call to a function that | 
 | # itself has a breakpoint set within it.  Run GDB and check what happens | 
 | # when GDB hits the nested breakpoint. | 
 | proc_with_prefix run_bp_cond_hits_breakpoint { target_async target_non_stop } { | 
 |     if { [start_gdb_and_runto_main $target_async \ | 
 | 	      $target_non_stop] == -1 } { | 
 | 	return | 
 |     } | 
 |  | 
 |     # Setup the conditional breakpoint, include a call to | 
 |     # 'function_with_breakpoint' in which we will shortly place a | 
 |     # breakpoint. | 
 |     gdb_breakpoint \ | 
 | 	"${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 0) && function_with_breakpoint ())" | 
 |     set bp_1_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ | 
 | 		      "get number of conditional breakpoint"] | 
 |  | 
 |     gdb_breakpoint "${::srcfile}:${::nested_bp_line}" | 
 |     set bp_2_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ | 
 | 		      "get number of nested breakpoint"] | 
 |  | 
 |     gdb_test "continue" \ | 
 | 	[multi_line \ | 
 | 	     "Continuing\\." \ | 
 | 	     ".*" \ | 
 | 	     "Thread ${::decimal} \"infcall-from-bp\" hit Breakpoint ${bp_2_num}, function_with_breakpoint \\(\\) at \[^\r\n\]+:${::nested_bp_line}" \ | 
 | 	     "${::decimal}\\s+\[^\r\n\]+Nested breakpoint\[^\r\n\]+" \ | 
 | 	     "Error in testing condition for breakpoint ${bp_1_num}:" \ | 
 | 	     "The program being debugged stopped while in a function called from GDB\\." \ | 
 | 	     "Evaluation of the expression containing the function" \ | 
 | 	     "\\(function_with_breakpoint\\) will be abandoned\\." \ | 
 | 	     "When the function is done executing, GDB will silently stop\\."] | 
 | } | 
 |  | 
 | foreach_with_prefix target_async { "on" "off" } { | 
 |     foreach_with_prefix target_non_stop { "on" "off" } { | 
 | 	run_condition_test "exactly one thread is hit" \ | 
 | 	    1 "is_matching_tid (arg, 1)" \ | 
 | 	    $target_async $target_non_stop | 
 | 	run_condition_test "exactly two threads are hit" \ | 
 | 	    2 "(is_matching_tid (arg, 0) || is_matching_tid (arg, 2))" \ | 
 | 	    $target_async $target_non_stop | 
 | 	run_condition_test "all three threads are hit" \ | 
 | 	    3 "return_true ()" \ | 
 | 	    $target_async $target_non_stop | 
 | 	run_condition_test "no thread is hit" \ | 
 | 	    0 "return_false ()" \ | 
 | 	    $target_async $target_non_stop | 
 |  | 
 | 	run_kill_and_restart_test $target_async $target_non_stop | 
 | 	run_bp_cond_segfaults $target_async $target_non_stop | 
 | 	run_bp_cond_hits_breakpoint $target_async $target_non_stop | 
 |     } | 
 | } |