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-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/>.
# 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}
}
}
}
|