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