blob: b93cfe69c7f4c9c4426796c481c8d2ef65c8199c [file] [log] [blame] [edit]
# 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}
}
}
}