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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
|
# Copyright 2021-2025 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/>.
# Test performing a 'stepi' over a clone syscall instruction.
# This test relies on us being able to spot syscall instructions in
# disassembly output. For now this is only implemented for x86-64.
require {istarget x86_64-*-*}
standard_testfile
if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
{debug pthreads additional_flags=-static}] } {
return
}
if {![runto_main]} {
return
}
# Arrange to catch the 'clone' syscall, run until we catch the
# syscall, and try to figure out the address of the actual syscall
# instruction so we can place a breakpoint at this address.
gdb_test_multiple "catch syscall group:process" "catch process syscalls" {
-re "The feature \'catch syscall\' is not supported.*\r\n$gdb_prompt $" {
unsupported $gdb_test_name
return
}
-re "Can not parse XML syscalls information; XML support was disabled at compile time.*\r\n$gdb_prompt $" {
unsupported $gdb_test_name
return
}
-re ".*$gdb_prompt $" {
pass $gdb_test_name
}
}
set re_loc1 "$hex in (__)?clone\[23\]? \\(\\)"
set re_loc2 "$decimal\[ \t\]+in \[^\r\n\]+"
set re_loc3 "(__)?clone\[23\]? \\(\\) at \[^:\]+:$decimal"
gdb_test "continue" \
"Catchpoint $decimal \\(call to syscall clone\[23\]?\\), ($re_loc1|$re_loc3).*"
# Return true if INSN is a syscall instruction.
proc is_syscall_insn { insn } {
if [istarget x86_64-*-* ] {
return { $insn == "syscall" }
} else {
error "port me"
}
}
# A list of addresses with syscall instructions.
set syscall_addrs {}
# Get list of addresses with syscall instructions.
gdb_test_multiple "disassemble" "" {
-re "Dump of assembler code for function \[^\r\n\]+:\r\n" {
exp_continue
}
-re "^(?:=>)?\\s+(${hex})\\s+<\\+${decimal}>:\\s+(\[^\r\n\]+)\r\n" {
set addr $expect_out(1,string)
set insn [string trim $expect_out(2,string)]
if [is_syscall_insn $insn] {
verbose -log "Found a syscall at: $addr"
lappend syscall_addrs $addr
}
exp_continue
}
-re "^End of assembler dump\\.\r\n$gdb_prompt $" {
if { [llength $syscall_addrs] == 0 } {
unsupported "no syscalls found"
return -1
}
}
}
# The test proc. NON_STOP and DISPLACED are either 'on' or 'off', and are
# used to configure how GDB starts up. THIRD_THREAD is either true or false,
# and is used to configure the inferior.
proc test {non_stop displaced third_thread} {
global binfile srcfile
global syscall_addrs
global GDBFLAGS
global gdb_prompt hex decimal
for { set i 0 } { $i < 3 } { incr i } {
with_test_prefix "i=$i" {
# Arrange to start GDB in the correct mode.
save_vars { GDBFLAGS } {
append GDBFLAGS " -ex \"set non-stop $non_stop\""
append GDBFLAGS " -ex \"set displaced $displaced\""
clean_restart $binfile
}
runto_main
# Setup breakpoints at all the syscall instructions we
# might hit. Only issue one pass/fail to make tests more
# comparable between systems.
set test "break at syscall insns"
foreach addr $syscall_addrs {
if {[gdb_test -nopass "break *$addr" \
".*" \
$test] != 0} {
return
}
}
# If we got here, all breakpoints were set successfully.
# We used -nopass above, so issue a pass now.
pass $test
# Continue until we hit the syscall.
gdb_test "continue"
if { $third_thread } {
gdb_test_no_output "set start_third_thread=1"
}
set stepi_error_count 0
set stepi_new_thread_count 0
set thread_1_stopped false
set thread_2_stopped false
set seen_prompt false
set hello_first_thread false
# The program is now stopped at main, but if testing
# against GDBserver, inferior_spawn_id is GDBserver's
# spawn_id, and the GDBserver output emitted before the
# program stopped isn't flushed unless we explicitly do
# so, because it is on a different spawn_id. We could try
# flushing it now, to avoid confusing the following tests,
# but that would have to be done under a timeout, and
# would thus slow down the testcase. Instead, if inferior
# output goes to a different spawn id, then we don't need
# to wait for the first message from the inferior with an
# anchor, as we know consuming inferior output won't
# consume GDB output. OTOH, if inferior output is coming
# out on GDB's terminal, then we must use an anchor,
# otherwise matching inferior output without one could
# consume GDB output that we are waiting for in regular
# expressions that are written after the inferior output
# regular expression match.
if {$::inferior_spawn_id != $::gdb_spawn_id} {
set anchor ""
} else {
set anchor "^"
}
gdb_test_multiple "stepi" "" {
-re "^stepi\r\n" {
verbose -log "XXX: Consume the initial command"
exp_continue
}
-re "^\\\[New Thread\[^\r\n\]+\\\]\r\n" {
verbose -log "XXX: Consume new thread line"
incr stepi_new_thread_count
exp_continue
}
-re "^\\\[Switching to Thread\[^\r\n\]+\\\]\r\n" {
verbose -log "XXX: Consume switching to thread line"
exp_continue
}
-re "^\\s*\r\n" {
verbose -log "XXX: Consume blank line"
exp_continue
}
-i $::inferior_spawn_id
-re "${anchor}Hello from the first thread\\.\r\n" {
set hello_first_thread true
verbose -log "XXX: Consume first worker thread message"
if { $third_thread } {
# If we are going to start a third thread then GDB
# should hit the breakpoint in clone before printing
# this message.
incr stepi_error_count
}
if { !$seen_prompt } {
exp_continue
}
}
-re "^Hello from the third thread\\.\r\n" {
# We should never see this message.
verbose -log "XXX: Consume third worker thread message"
incr stepi_error_count
if { !$seen_prompt } {
exp_continue
}
}
-i $::gdb_spawn_id
-re "^($::re_loc1|$::re_loc2)\r\n" {
verbose -log "XXX: Consume stop location line"
set thread_1_stopped true
if { !$seen_prompt } {
verbose -log "XXX: Continuing to look for the prompt"
exp_continue
}
}
-re "^$gdb_prompt " {
verbose -log "XXX: Consume the final prompt"
gdb_assert { $stepi_error_count == 0 }
gdb_assert { $stepi_new_thread_count == 1 }
set seen_prompt true
if { $third_thread } {
if { $non_stop } {
# In non-stop mode if we are trying to start a
# third thread (from the second thread), then the
# second thread should hit the breakpoint in clone
# before actually starting the third thread. And
# so, at this point both thread 1, and thread 2
# should now be stopped.
if { !$thread_1_stopped || !$thread_2_stopped } {
verbose -log "XXX: Continue looking for an additional stop event"
exp_continue
}
} else {
# All stop mode. Something should have stoppped
# by now otherwise we shouldn't have a prompt, but
# we can't know which thread will have stopped as
# that is a race condition.
gdb_assert { $thread_1_stopped || $thread_2_stopped }
}
}
if {$non_stop && !$hello_first_thread} {
exp_continue
}
}
-re "^Thread 2\[^\r\n\]+ hit Breakpoint $decimal, ($::re_loc1|$::re_loc3)\r\n" {
verbose -log "XXX: Consume thread 2 hit breakpoint"
set thread_2_stopped true
if { !$seen_prompt } {
verbose -log "XXX: Continuing to look for the prompt"
exp_continue
}
}
-re "^PC register is not available\r\n" {
# This is the error we'd see for remote targets.
verbose -log "XXX: Consume error line"
incr stepi_error_count
exp_continue
}
-re "^Couldn't get registers: No such process\\.\r\n" {
# This is the error we see'd for native linux
# targets.
verbose -log "XXX: Consume error line"
incr stepi_error_count
exp_continue
}
}
# Ensure we are back at a GDB prompt, resynchronise.
verbose -log "XXX: Have completed scanning the 'stepi' output"
gdb_test "p 1 + 2 + 3" " = 6"
# Check the number of threads we have, it should be exactly two.
set thread_count 0
set bad_threads 0
# Build up our expectations for what the current thread state
# should be. Thread 1 is the easiest, this is the thread we are
# stepping, so this thread should always be stopped, and should
# always still be in clone.
set match_code {}
lappend match_code {
-re "\\*?\\s+1\\s+Thread\[^\r\n\]+($::re_loc1|$::re_loc3)\r\n" {
incr thread_count
exp_continue
}
}
# What state should thread 2 be in?
if { $non_stop == "on" } {
if { $third_thread } {
# With non-stop mode on, and creation of a third thread
# having been requested, we expect Thread 2 to exist, and
# be stopped at the breakpoint in clone (just before the
# third thread is actually created).
lappend match_code {
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+($::re_loc1|$::re_loc3)\r\n" {
incr thread_count
exp_continue
}
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
incr thread_count
incr bad_threads
exp_continue
}
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
verbose -log "XXX: thread 2 is bad, unknown state"
incr thread_count
incr bad_threads
exp_continue
}
}
} else {
# With non-stop mode on, and no third thread having been
# requested, then we expect Thread 2 to exist, and still
# be running.
lappend match_code {
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
incr thread_count
exp_continue
}
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
verbose -log "XXX: thread 2 is bad, unknown state"
incr thread_count
incr bad_threads
exp_continue
}
}
}
} else {
# With non-stop mode off then we expect Thread 2 to exist, and
# be stopped. We don't have any guarantee about where the
# thread will have stopped though, so we need to be vague.
lappend match_code {
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
verbose -log "XXX: thread 2 is bad, unexpectedly running"
incr thread_count
incr bad_threads
exp_continue
}
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+_start\[^\r\n\]+\r\n" {
# We know that the thread shouldn't be stopped
# at _start, though. This is the location of
# the scratch pad on Linux at the time of
# writting.
verbose -log "XXX: thread 2 is bad, stuck in scratchpad"
incr thread_count
incr bad_threads
exp_continue
}
-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
incr thread_count
exp_continue
}
}
}
# We don't expect to ever see a thread 3. Even when we are
# requesting that this third thread be created, thread 2, the
# thread that creates thread 3, should stop before executing the
# clone syscall. So, if we do ever see this then something has
# gone wrong.
lappend match_code {
-re "\\s+3\\s+Thread\[^\r\n\]+\r\n" {
incr thread_count
incr bad_threads
exp_continue
}
}
lappend match_code {
-re "$gdb_prompt $" {
gdb_assert { $thread_count == 2 }
gdb_assert { $bad_threads == 0 }
}
}
set match_code [join $match_code]
gdb_test_multiple "info threads" "" $match_code
}
}
}
# Run the test in all suitable configurations.
foreach_with_prefix third_thread { false true } {
foreach_with_prefix non-stop { "on" "off" } {
foreach_with_prefix displaced { "off" "on" } {
test ${non-stop} ${displaced} ${third_thread}
}
}
}
|