aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp
blob: bffbb2900719cdc1d0b824344932f99dfda0d99d (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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# Copyright 2023 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/>.

# Check that the =breakpoint-deleted notification for a thread-specific
# breakpoint is sent as soon as the related thread exits, and not when the
# inferior next stops.
#
# This test is based on gdb.threads/thread-bp-deleted.exp.

load_lib mi-support.exp
set MIFLAGS "-i=mi"

# We need to do things a little differently when using the remote protocol.
set is_remote \
    [expr [target_info exists gdb_protocol] \
	 && ([string equal [target_info gdb_protocol] "remote"] \
	     || [string equal [target_info gdb_protocol] "extended-remote"])]

standard_testfile

if { [build_executable "failed to prepare" $testfile $srcfile \
	  {debug pthreads}] } {
    return -1
}

foreach_mi_ui_mode mode {
    mi_gdb_exit

    if {$mode eq "separate"} {
	set start_ops "separate-mi-tty"
    } else {
	set start_ops ""
    }

    # Restart, but enable non-stop mode, we need it for background
    # execution.
    save_vars { GDBFLAGS } {
	append GDBFLAGS " -ex \"maint set target-non-stop on\""
	append GDBFLAGS " -ex \"set mi-async on\""
	mi_clean_restart $binfile $start_ops
    }

    mi_runto_main

    if {![mi_detect_async]} {
	unsupported "async-mode is required"
	continue
    }

    mi_delete_breakpoints

    # Place a breakpoint on 'breakpt' and run to this breakpoint.
    mi_create_breakpoint "breakpt" "place breakpoint on breakpt"
    set breakpt_num [mi_get_valueof "/d" "\$bpnum" "INVALID" \
			"get number for breakpt breakpoint"]
    mi_execute_to "exec-continue" "breakpoint-hit" "breakpt" "" \
	".*" ".*" {"" "disp=\"keep\""} \
	"continue to breakpoint in breakpt"

    # Now drain all the pending output from the CLI if we are using a separate
    # UI.
    if {$mode eq "separate"} {
	with_spawn_id $gdb_main_spawn_id {
	    gdb_test_multiple "" "drain CLI output upto breakpoint" {
		-re "Thread 1 \[^\r\n\]+ hit Breakpoint $decimal,\
		      breakpt \\(\\) at\
		      \[^\r\n\]+\r\n$decimal\\s+\[^\r\n\]+\r\n" {
		    pass $gdb_test_name
		}
	    }
	}
    }

    # This is just for convenience, this refers to the second thread the
    # inferior spawns.
    set worker_thread 2

    # Create a thread-specific breakpoint.
    mi_create_breakpoint "-p $worker_thread main" \
	"place thread breakpoint on main" \
	-thread "$worker_thread"
    set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID" \
		  "get number for thread-specific breakpoint"]

    set loc1 [mi_make_breakpoint -number "$breakpt_num"]
    set loc2 [mi_make_breakpoint -number "$bpnum" -thread "$worker_thread"]
    set table_2_locs [mi_make_breakpoint_table [list $loc1 $loc2]]
    set table_1_locs [mi_make_breakpoint_table [list $loc1]]

    mi_gdb_test "-break-info" \
	"\\^done,$table_2_locs" \
	"-break-info, expecting two locations"

    # Resume the inferior, at this point the inferior will spin while
    # we interact with it.
    mi_send_resuming_command "exec-continue" "continue"

    # Look for the thread-exited notification and the breakpoint-deleted
    # notification.  When using a single UI we see both the MI and CLI
    # messages.  When using a separate MI UI we only see the MI messages.
    set saw_cli_thread_exited false
    set saw_mi_thread_exited false
    set saw_cli_bp_deleted false
    set saw_mi_bp_deleted false

    # When running with a remote target, the thread-exited event doesn't
    # appear to be pushed from the target to GDB; instead GDB has to fetch the
    # thread list from the target and spot that a thread exited.
    #
    # In order to achieve this, when running with a remote target we run the
    # '-thread-info 99' command.  There isn't a thread 99, but GDB doesn't
    # know that until it fetches the thread list.  By fetching the thread list
    # GDB will spot that the thread we are interested in has exited.
    if {$is_remote} {
	set cmd "-thread-info 99"
	set attempt_count 5
    } else {
	set cmd ""
	set attempt_count 0
    }


    gdb_test_multiple $cmd "collect thread exited output" \
	-prompt "$::mi_gdb_prompt$" {

	-re "^~\"\\\[Thread \[^\r\n\]+ exited\\\]\\\\n\"\r\n" {
	    set saw_cli_thread_exited true
	    exp_continue
	}

	-re "^~\"Thread-specific breakpoint $bpnum deleted -\
	     thread $worker_thread no longer in the thread list\\.\\\\n\"\r\n" {
	    set saw_cli_bp_deleted true
	    exp_continue
	}

	-re "^=thread-exited,id=\"$worker_thread\",group-id=\"i1\"\r\n" {
	    set saw_mi_thread_exited true

	    # The order of the MI notifications depends on the order in which
	    # the observers where registered within GDB.  If we have not seen
	    # the other MI notification yet then keep looking.
	    #
	    # Additionally, for remote targets, we're going to wait for the
	    # output of the '-thread-info 99' command before we check the
	    # results.
	    if {!$saw_mi_bp_deleted || $is_remote} {
		exp_continue
	    }

	    # We get here with a native target; check we saw all the output
	    # that we expected.
	    if {$mode eq "separate"} {
		gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
			     && !$saw_cli_thread_exited \
			     && !$saw_cli_bp_deleted } \
		    $gdb_test_name
	    } else {
		gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
			     && $saw_cli_thread_exited \
			     && $saw_cli_bp_deleted } \
		    $gdb_test_name
	    }
	}

	-re "^=breakpoint-deleted,id=\"3\"\r\n" {
	    set saw_mi_bp_deleted true

	    # The order of the MI notifications depends on the order in which
	    # the observers where registered within GDB.  If we have not seen
	    # the other MI notification yet then keep looking.
	    #
	    # Additionally, for remote targets, we're going to wait for the
	    # output of the '-thread-info 99' command before we check the
	    # results.
	    if {!$saw_mi_thread_exited || $is_remote} {
		exp_continue
	    }

	    # We get here with a native target; check we saw all the output
	    # that we expected.
	    if {$mode eq "separate"} {
		gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
			     && !$saw_cli_thread_exited \
			     && !$saw_cli_bp_deleted } \
		    $gdb_test_name
	    } else {
		gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
			     && $saw_cli_thread_exited \
			     && $saw_cli_bp_deleted } \
		    $gdb_test_name
	    }
	}

	-re "^-thread-info 99\r\n" {
	    if {!$is_remote} {
		fail "$gdb_test_name (unexpected output)"
	    }
	    # This is the command being echoed back, ignore it.
	    exp_continue
	}

	-re "^\\^done,threads=\\\[\\\]\r\n$::mi_gdb_prompt$" {

	    # This is the result of the '-thread-info 99' trick, which is only
	    # used in remote mode.  If we see this in native mode then
	    # something has gone wrong.
	    if {!$is_remote} {
		fail "$gdb_test_name (unexpected output)"
	    }

	    # If we've not seen any of the expected output yet then maybe the
	    # remote thread just hasn't exited yet.  Wait a short while and
	    # try again.
	    if { !$saw_mi_thread_exited && !$saw_mi_bp_deleted \
		     && !$saw_cli_thread_exited && !$saw_cli_bp_deleted \
		     && $attempt_count > 0 } {
		sleep 1
		incr attempt_count -1
		send_gdb "$cmd\n"
		exp_continue
	    }

	    # The output has arrived!  Check how we did.  There are other bugs
	    # that come into play here which change what output we'll see.
	    gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
			     && $saw_cli_thread_exited \
			     && $saw_cli_bp_deleted } $gdb_test_name
	}
    }

    # When the MI is running on a separate UI the CLI message will be seen
    # over there, but only if we are not running remote.  When we are running
    # remote then the thread-exited event will only be triggered as a result
    # of user triggering a refresh of the thread list (hence the '-thread-info
    # 99' trick above).  By typing a command we change the current UI to the
    # terminal we are typing at, as a result these CLI style message will
    # actually appear on the MI when using a remote target.
    if {$mode eq "separate" && !$is_remote} {
	with_spawn_id $gdb_main_spawn_id {
	    set saw_thread_exited false
	    gdb_test_multiple "" "collect cli thread exited output" {
		-re "\\\[Thread \[^\r\n\]+ exited\\\]\r\n" {
		    set saw_thread_exited true
		    exp_continue
		}

		-re "^Thread-specific breakpoint $bpnum deleted -\
		     thread $worker_thread no longer in the thread list\\.\r\n" {
		    gdb_assert { $saw_thread_exited } \
			$gdb_test_name
		}
	    }
	}
    }

    mi_gdb_test "-break-info" \
	"\\^done,$table_1_locs" \
	"-break-info, expecting one location"

    # Set 'do_spin' to zero, this allows the inferior to progress again; we
    # should then hit the breakpoint in 'breakpt' again.
    mi_gdb_test "set var do_spin = 0" \
	[multi_line \
	     ".*=memory-changed,thread-group=\"i${decimal}\".addr=\"${hex}\",len=\"${hex}\"" \
	     "\\^done"] \
	"set do_spin variable in inferior, inferior should now finish"
    mi_expect_stop "breakpoint-hit" "breakpt" ".*" ".*" "$::decimal" \
	{"" "disp=\"keep\""} "stop in breakpt at the end of the test"
}