Home | History | Annotate | Line # | Download | only in gdb.threads
      1 # Copyright 2016-2024 Free Software Foundation, Inc.
      2 # This program is free software; you can redistribute it and/or modify
      3 # it under the terms of the GNU General Public License as published by
      4 # the Free Software Foundation; either version 3 of the License, or
      5 # (at your option) any later version.
      6 #
      7 # This program is distributed in the hope that it will be useful,
      8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
      9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     10 # GNU General Public License for more details.
     11 #
     12 # You should have received a copy of the GNU General Public License
     13 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     14 
     15 # This test spawns a few threads that immediately exit the whole
     16 # process.  On targets where the debugger needs to detach from each
     17 # thread individually (such as on the Linux kernel), the debugger must
     18 # handle the case of the process exiting while the detach is ongoing.
     19 #
     20 # Similarly, the process can also be killed from outside the debugger
     21 # (e.g., with SIGKILL), _before_ the user requests a detach.  The
     22 # debugger must likewise detach gracefully.
     23 #
     24 # The testcase actually builds two variants of the test program:
     25 # single-process, and multi-process.  In the multi-process variant,
     26 # the test program forks, and it's the fork child that spawns threads
     27 # that exit just while the process is being detached from.  The fork
     28 # parent waits for its child to exit, so if GDB fails to detach from
     29 # the child correctly, the parent hangs.  Because continuing the
     30 # parent can mask failure to detach from the child correctly (e.g.,
     31 # due to waitpid(-1,...) calls deep in the target layers managing to
     32 # reap the child), we try immediately detaching from the parent too,
     33 # and observing whether the parent exits via standard output.
     34 #
     35 # Normally, if testing with "target remote" against gdbserver, then
     36 # after detaching from all attached processes, gdbserver exits.
     37 # However, when gdbserver detaches from a process that is its own
     38 # direct child, gdbserver does not exit immediately.  Instead it
     39 # "joins" (waits for) the child, only exiting when the child itself
     40 # exits too.  Thus, on Linux, if gdbserver fails to detach from the
     41 # zombie child's threads correctly (or rather, reap them), it'll hang,
     42 # because the leader thread will only return an exit status after all
     43 # threads are reaped.  We test that as well.
     44 
     45 standard_testfile
     46 
     47 # Test that GDBserver exits.
     48 
     49 proc test_server_exit {} {
     50     global server_spawn_id
     51 
     52     set test "server exits"
     53     gdb_expect {
     54 	-i $server_spawn_id
     55 	eof {
     56 	    pass $test
     57 	    wait -i $server_spawn_id
     58 	    unset server_spawn_id
     59 	}
     60 	timeout {
     61 	    fail "$test (timeout)"
     62 	}
     63     }
     64 }
     65 
     66 # If RESULT is not zero, make the caller return.
     67 
     68 proc return_if_fail { result } {
     69     if {$result != 0} {
     70 	return -code return
     71     }
     72 }
     73 
     74 # Detach from a process, and ensure that it exits after detaching.
     75 # This relies on inferior I/O.  INF_OUTPUT_RE is the pattern that
     76 # matches the expected inferior output.
     77 
     78 proc detach_and_expect_exit {inf_output_re test} {
     79     global decimal
     80     global gdb_spawn_id
     81     global inferior_spawn_id
     82     global gdb_prompt
     83 
     84     return_if_fail [gdb_test_multiple "detach" $test {
     85 	-re "Detaching from .*, process $decimal" {
     86 	}
     87     }]
     88 
     89     # Use an indirect spawn id list, and remove inferior spawn id from
     90     # the expected output as soon as it matches, so that if
     91     # $inf_inferior_spawn_id is $server_spawn_id and we're testing in
     92     # "target remote" mode, the eof caused by gdbserver exiting is
     93     # left for the caller to handle.
     94     global daee_spawn_id_list
     95     set daee_spawn_id_list "$inferior_spawn_id $gdb_spawn_id"
     96 
     97     set saw_prompt 0
     98     set saw_inf_exit 0
     99     while { !$saw_prompt || ! $saw_inf_exit } {
    100 	# We don't know what order the interesting things will arrive in.
    101 	# Using a pattern of the form 'x|y|z' instead of -re x ... -re y
    102 	# ... -re z ensures that expect always chooses the match that
    103 	# occurs leftmost in the input, and not the pattern appearing
    104 	# first in the script that occurs anywhere in the input, so that
    105 	# we don't skip anything.
    106 	return_if_fail [gdb_test_multiple "" $test {
    107 	    -i daee_spawn_id_list
    108 	    -re "($inf_output_re)|($gdb_prompt )" {
    109 		if {[info exists expect_out(1,string)]} {
    110 		    verbose -log "saw inferior exit"
    111 		    set saw_inf_exit 1
    112 		    set daee_spawn_id_list "$gdb_spawn_id"
    113 		} elseif {[info exists expect_out(2,string)]} {
    114 		    verbose -log "saw prompt"
    115 		    set saw_prompt 1
    116 		    set daee_spawn_id_list "$inferior_spawn_id"
    117 		}
    118 		array unset expect_out
    119 	    }
    120 	}]
    121     }
    122 
    123     pass $test
    124 }
    125 
    126 # Run to _exit in the child.
    127 
    128 proc continue_to_exit_bp {} {
    129     gdb_breakpoint "_exit" temporary
    130     return [gdb_continue_to_breakpoint "_exit" ".*_exit.*"]
    131 }
    132 
    133 # If testing single-process, simply detach from the process.
    134 #
    135 # If testing multi-process, first detach from the child, then detach
    136 # from the parent and confirm that the parent exits, thus ensuring
    137 # we've detached from the child successfully, as the parent hangs in
    138 # its waitpid call otherwise.
    139 #
    140 # If connected with "target remote", make sure gdbserver exits.
    141 #
    142 # CMD indicates what to do with the parent after detaching the child.
    143 # Can be either "detach" to detach, or "continue", to continue to
    144 # exit.
    145 #
    146 # CHILD_EXIT indicates how is the child expected to exit.  Can be
    147 # either "normal" for normal exit, or "signal" for killed with signal
    148 # SIGKILL.
    149 #
    150 proc do_detach {multi_process cmd child_exit} {
    151     global decimal
    152     global server_spawn_id
    153 
    154     if {$child_exit == "normal"} {
    155 	set continue_re "exited normally.*"
    156 	set inf_output_re "exited, status=0"
    157     } elseif {$child_exit == "signal"} {
    158 	if {$multi_process} {
    159 	    set continue_re "exited with code 02.*"
    160 	} else {
    161 	    set continue_re "terminated with signal SIGKILL.*"
    162 	}
    163 	set inf_output_re "signaled, sig=9"
    164     } else {
    165 	error "unhandled \$child_exit: $child_exit"
    166     }
    167 
    168     set is_remote [expr {[target_info exists gdb_protocol]
    169 			 && [target_info gdb_protocol] == "remote"}]
    170 
    171     if {$multi_process} {
    172 	gdb_test "detach" "Detaching from .*, process $decimal\r\n\\\[Inferior $decimal \\(.*\\) detached\\\]" \
    173 	    "detach child"
    174 
    175 	gdb_test "inferior 1" "\[Switching to inferior $decimal\].*" \
    176 	    "switch to parent"
    177 
    178 	if {$cmd == "detach"} {
    179 	    # Make sure that detach works and that the parent process
    180 	    # exits cleanly.
    181 	    detach_and_expect_exit $inf_output_re "detach parent"
    182 	} elseif {$cmd == "continue"} {
    183 	    # Make sure that continuing works and that the parent process
    184 	    # exits cleanly.
    185 	    gdb_test "continue" $continue_re
    186 	} else {
    187 	    perror "unhandled command: $cmd"
    188 	}
    189     } else {
    190 	if $is_remote {
    191 	    set extra "\r\nEnding remote debugging\."
    192 	} else {
    193 	    set extra ""
    194 	}
    195 	if {$cmd == "detach"} {
    196 	    gdb_test "detach" "Detaching from .*, process ${decimal}\r\n\\\[Inferior $decimal \\(.*\\) detached\\\]$extra"
    197 	} elseif {$cmd == "continue"} {
    198 	    gdb_test "continue" $continue_re
    199 	} else {
    200 	    perror "unhandled command: $cmd"
    201 	}
    202     }
    203 
    204     # When connected in "target remote" mode, the server should exit
    205     # when there are no processes left to debug.
    206     if { $is_remote && [info exists server_spawn_id]} {
    207 	test_server_exit
    208     }
    209 }
    210 
    211 # Test detaching from a process that dies just while GDB is detaching.
    212 
    213 proc test_detach {multi_process cmd} {
    214     with_test_prefix "detach" {
    215 	global binfile
    216 
    217 	clean_restart ${binfile}
    218 
    219 	if ![runto_main] {
    220 	    return -1
    221 	}
    222 
    223 	if {$multi_process} {
    224 	    gdb_test_no_output "set detach-on-fork off"
    225 	    gdb_test_no_output "set follow-fork-mode child"
    226 	}
    227 
    228 	# Run to _exit in the child.
    229 	return_if_fail [continue_to_exit_bp]
    230 
    231 	do_detach $multi_process $cmd "normal"
    232     }
    233 }
    234 
    235 # Same as test_detach, except set a watchpoint before detaching.
    236 
    237 proc test_detach_watch {wp multi_process cmd} {
    238     if { $wp == "hw" && ![allow_hw_watchpoint_tests] } {
    239 	unsupported "hw watchpoint"
    240 	return
    241     }
    242     with_test_prefix "watchpoint:$wp" {
    243 	global binfile decimal
    244 
    245 	clean_restart ${binfile}
    246 
    247 	if ![runto_main] {
    248 	    return -1
    249 	}
    250 
    251 	if {$multi_process} {
    252 	    gdb_test_no_output "set detach-on-fork off"
    253 	    gdb_test_no_output "set follow-fork-mode child"
    254 
    255 	    gdb_breakpoint "child_function" temporary
    256 	    gdb_continue_to_breakpoint "child_function" ".*"
    257 	}
    258 
    259 	if { $wp == "hw" } {
    260 	    # Set a watchpoint in the child.
    261 	    gdb_test "watch globalvar" ".* watchpoint $decimal: globalvar"
    262 
    263 	    # Continue to the _exit breakpoint.  This arms the watchpoint
    264 	    # registers in all threads.  Detaching will thus need to clear
    265 	    # them out, and handle the case of the thread disappearing
    266 	    # while doing that (on targets that need to detach from each
    267 	    # thread individually).
    268 	    return_if_fail [continue_to_exit_bp]
    269 	} else {
    270 	    # Force software watchpoints.
    271 	    gdb_test_no_output "set can-use-hw-watchpoints 0"
    272 
    273 	    # As above, but flip order, other wise things take too long.
    274 	    return_if_fail [continue_to_exit_bp]
    275 	    gdb_test "watch globalvar" "Watchpoint $decimal: globalvar"
    276 
    277 	    if { $multi_process == 0 && $cmd == "continue" } {
    278 		setup_kfail "gdb/28375" "*-*-*"
    279 	    }
    280 	}
    281 
    282 	do_detach $multi_process $cmd "normal"
    283     }
    284 }
    285 
    286 # Test detaching from a process that dies _before_ GDB starts
    287 # detaching.
    288 
    289 proc test_detach_killed_outside {multi_process cmd} {
    290     with_test_prefix "killed outside" {
    291 	global binfile
    292 
    293 	clean_restart ${binfile}
    294 
    295 	if ![runto_main] {
    296 	    return -1
    297 	}
    298 
    299 	gdb_test_no_output "set breakpoint always-inserted on"
    300 
    301 	if {$multi_process} {
    302 	    gdb_test_no_output "set detach-on-fork off"
    303 	    gdb_test_no_output "set follow-fork-mode child"
    304 	}
    305 
    306 	# Run to _exit in the child.
    307 	return_if_fail [continue_to_exit_bp]
    308 
    309 	set childpid [get_integer_valueof "mypid" -1]
    310 	if { $childpid == -1 } {
    311 	    untested "failed to extract child pid"
    312 	    return -1
    313 	}
    314 
    315 	remote_exec target "kill -9 ${childpid}"
    316 
    317 	# Give it some time to die.
    318 	sleep 2
    319 
    320 	do_detach $multi_process $cmd "signal"
    321     }
    322 }
    323 
    324 # The test proper.  MULTI_PROCESS is true if testing the multi-process
    325 # variant.
    326 
    327 proc do_test {multi_process cmd} {
    328     global testfile srcfile binfile
    329 
    330     if {$multi_process && $cmd == "detach"
    331 	&& [target_info exists gdb,noinferiorio]} {
    332 	# This requires inferior I/O to tell whether both the parent
    333 	# and child exit successfully.
    334 	return
    335     }
    336 
    337     set binfile [standard_output_file ${testfile}-$multi_process-$cmd]
    338     set options {debug pthreads}
    339     if {$multi_process} {
    340 	lappend options "additional_flags=-DMULTIPROCESS"
    341     }
    342 
    343     if {[build_executable "failed to build" \
    344 	     $testfile-$multi_process-$cmd $srcfile $options] == -1} {
    345 	return -1
    346     }
    347 
    348     test_detach $multi_process $cmd
    349     foreach wp {"sw" "hw"} {
    350 	test_detach_watch $wp $multi_process $cmd
    351     }
    352     test_detach_killed_outside $multi_process $cmd
    353 }
    354 
    355 foreach multi_process {0 1} {
    356     set mode [expr {$multi_process ? "multi-process" : "single-process"}]
    357     foreach cmd {"detach" "continue"} {
    358 	with_test_prefix "$mode: $cmd" {
    359 	    do_test $multi_process $cmd
    360 	}
    361     }
    362 }
    363