summaryrefslogtreecommitdiff
path: root/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp
blob: 8d94d24f9b3a6da281e01ced894845644f78e831 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# Copyright 2022 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
# sometime 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]} {
	fail "run to main"
	return -1
    }

    return 0
}

# Run a test of GDB's conditional breakpoints, where the conditions include
# inferior function calls.
#
# CONDITION is combined (with &&) to some additional logic, and 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 twice"
    }
}

# 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_test "start" ".*"
}

# 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 unwindonsignal 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
    }
}