From: Pedro Alves Date: Mon, 14 Dec 2020 12:07:20 +0000 (+0000) Subject: Testcase for detaching while stepping over breakpoint X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=a71501e25ff14918d7876f4905be0df9dd3ae29a;p=binutils-gdb.git Testcase for detaching while stepping over breakpoint This adds a testcase that exercises detaching while GDB is stepping over a breakpoint, in all combinations of: - maint target non-stop off/on - set non-stop on/off - displaced stepping on/off This exercises the bugs fixed in the previous 8 patches. gdb/testsuite/ChangeLog: * gdb.threads/detach-step-over.c: New file. * gdb.threads/detach-step-over.exp: New file. --- diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 75fcf3e77e4..62c06d0f52c 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2021-02-03 Pedro Alves + + * gdb.threads/detach-step-over.c: New file. + * gdb.threads/detach-step-over.exp: New file. + 2021-02-03 Pedro Alves * gdb.threads/attach-non-stop.c: New file. diff --git a/gdb/testsuite/gdb.threads/detach-step-over.c b/gdb/testsuite/gdb.threads/detach-step-over.c new file mode 100644 index 00000000000..13db9f60b04 --- /dev/null +++ b/gdb/testsuite/gdb.threads/detach-step-over.c @@ -0,0 +1,112 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2021 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 . */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +/* Number of threads we'll create. */ +int n_threads = 10; + +int mypid; + +static void +setup_done (void) +{ +} + +/* Entry point for threads. Loops forever. */ + +void * +thread_func (void *arg) +{ + /* Avoid setting the breakpoint at an instruction that wouldn't + require a fixup phase, like a branch/jump. In such a case, even + if GDB manages to detach the inferior with an incomplete + displaced step, GDB inferior may still not crash. A breakpoint + at a line that increments a variable is good bet that we end up + setting a breakpoint at an instruction that will require a fixup + phase to move the PC from the scratch pad to the instruction + after the breakpoint. */ + volatile unsigned counter = 0; + + while (1) + { + counter++; /* Set breakpoint here. */ + counter++; + counter++; + } + + return NULL; +} + +/* Allow for as much timeout as DejaGnu wants, plus a bit of + slack. */ +#define SECONDS (TIMEOUT + 20) + +/* We'll exit after this many seconds. */ +unsigned int seconds_left = SECONDS; + +/* GDB sets this whenever it's about to start a new detach/attach + sequence. We react by resetting the seconds-left counter. */ +volatile int again = 0; + +int +main (int argc, char **argv) +{ + int i; + + signal (SIGUSR1, SIG_IGN); + + mypid = getpid (); + setup_done (); + + if (argc > 1) + n_threads = atoi (argv[1]); + + /* Spawn the test threads. */ + for (i = 0; i < n_threads; ++i) + { + pthread_t child; + int rc; + + rc = pthread_create (&child, NULL, thread_func, NULL); + assert (rc == 0); + } + + /* Exit after a while if GDB is gone/crashes. But wait long enough + for one attach/detach sequence done by the .exp file. */ + while (--seconds_left > 0) + { + sleep (1); + + if (again) + { + /* GDB should be reattaching soon. Restart the timer. */ + again = 0; + seconds_left = SECONDS; + } + } + + printf ("timeout, exiting\n"); + return 0; +} diff --git a/gdb/testsuite/gdb.threads/detach-step-over.exp b/gdb/testsuite/gdb.threads/detach-step-over.exp new file mode 100644 index 00000000000..8c58aebad8a --- /dev/null +++ b/gdb/testsuite/gdb.threads/detach-step-over.exp @@ -0,0 +1,290 @@ +# Copyright 2021 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 detaching from a process that is running and has threads +# constantly hitting a breakpoint and stepping over it, in all +# combinations of: +# +# - maint target non-stop off/on +# - set non-stop on/off +# - displaced stepping on/off +# +# This stresses the edge cases of detaching while a displaced step or +# an in-line step over are in progress. +# +# A fail mode is that the inferior process dies after being detached. +# This can happen because e.g.: +# +# - GDB leaves a breakpoint installed behind, or +# +# - GDB leaves a thread running in the displaced step scratch buffer. +# With no debugger around to run the finish step, the thread runs +# off of the scratch buffer, with undefined results. +# +# To exercise this, the testcase reattaches to the process shortly +# after detaching, ensuring the process is still alive and well. +# +# In addition, since GDB may pause threads of all processes for +# stepping over a breakpoint, it needs to re-resume all threads if it +# detaches from the process that was just stepping over the +# breakpoint. To ensure that, the testcase actually runs a second +# process at the same time as the one that is used to test detaching. +# After the first process is detached, the testcase sends a SIGUSR1 to +# the second process. If threads failed to be resumed, then the +# SIGUSR1 is never reported to the user, resulting in timeout. The +# threads of this second process will also be constantly stepping over +# a breakpoint, which has helped with exposing further corner case +# bugs. + +if {![can_spawn_for_attach]} { + return 0 +} + +standard_testfile + +set bp_lineno [gdb_get_line_number "Set breakpoint here"] + +# The test proper. See description above. +proc test {condition_eval target_non_stop non_stop displaced} { + global binfile srcfile + global gdb_prompt + global decimal + global bp_lineno + global GDBFLAGS + + # Number of threads started by the program. + set n_threads 10 + + save_vars { GDBFLAGS } { + append GDBFLAGS " -ex \"maint set target-non-stop $target_non_stop\"" + append GDBFLAGS " -ex \"set non-stop $non_stop\"" + append GDBFLAGS " -ex \"set displaced $displaced\"" + append GDBFLAGS " -ex \"set schedule-multiple on\"" + clean_restart $binfile + } + + set test_spawn_id [spawn_wait_for_attach $binfile] + set testpid [spawn_id_get_pid $test_spawn_id] + + set any "\[^\r\n\]*" + + gdb_test "add-inferior" "Added inferior 2.*" + gdb_test "inferior 2" "Switching to .*" + + gdb_load $binfile + if ![runto setup_done] then { + fail "can't run to setup_done" + kill_wait_spawned_process $test_spawn_id + return + } + + gdb_test_no_output "set breakpoint condition-evaluation $condition_eval" + + # Get the PID of the test process. + set pid_inf2 "" + gdb_test_multiple "p mypid" "get pid of inferior 2" { + -re " = ($decimal)\r\n$gdb_prompt $" { + set pid_inf2 $expect_out(1,string) + pass $gdb_test_name + } + } + + set attempts 3 + for {set attempt 1} { $attempt <= $attempts } { incr attempt } { + with_test_prefix "iter $attempt" { + gdb_test "inferior 1" "Switching to .*" + + set attached 0 + set eperm 0 + set test "attach" + gdb_test_multiple "attach $testpid" $test { + -re "new threads in iteration" { + # Seen when "set debug libthread_db" is on. + exp_continue + } + -re "is a zombie - the process has already terminated.*$gdb_prompt " { + fail $gdb_test_name + } + -re "Unable to attach: .*$gdb_prompt " { + fail $gdb_test_name + } + -re "Attaching to program.*process $testpid.*$gdb_prompt " { + pass $test + set attached 1 + } + } + + if {!$attached} { + kill_wait_spawned_process $test_spawn_id + return + } + + if {$non_stop} { + # In non-stop, we will see one stop per thread after + # the prompt. + set stops 0 + set tid_re "$::decimal\.$::decimal" + set test "seen all stops" + for {set thread 1} { $thread <= $n_threads } { incr thread } { + if {[gdb_test_multiple "" $test { + -re "Thread ${tid_re} ${any} stopped" { + incr stops + } + }] != 0} { + break + } + } + + # If we haven't seen all stops, then the + # gdb_test_multiple in the loop above will have + # already issued a FAIL. + if {$stops != $n_threads} { + kill_wait_spawned_process $test_spawn_id + return + } + pass $test + } + + # Set threads stepping over a breakpoint continuously. + gdb_test "break $srcfile:$bp_lineno if 0" "Breakpoint.*" \ + "break LOC if 0" + + if {$attempt < $attempts} { + # Kick the time out timer for another round. + gdb_test "print again = 1" " = 1" "reset timer in the inferior" + # Show the time we had left in the logs, in case + # something goes wrong. + gdb_test "print seconds_left" " = .*" + } + + if {$non_stop} { + set cont_cmd "continue -a &" + } else { + set cont_cmd "continue &" + } + + set cont_cmd_re [string_to_regexp $cont_cmd] + gdb_test_multiple $cont_cmd "" { + -re "^$cont_cmd_re\r\nContinuing\.\r\n$gdb_prompt " { + pass $gdb_test_name + } + } + + # Wait a bit, to give time for the threads to hit the + # breakpoint. + sleep 1 + + set running_count 0 + set interrupted 0 + gdb_test_multiple "info threads" "all threads running" { + -re "\\(running\\)" { + incr running_count + exp_continue + } + -re "Cannot execute this command while the target is running.*$gdb_prompt $" { + # Testing against a remote server that doesn't do + # non-stop mode. Explicitly interrupt. This + # doesn't test the same code paths in GDB, but + # it's still something. + set interrupted 1 + gdb_test_multiple "interrupt" "" { + -re "$gdb_prompt " { + gdb_test_multiple "" $gdb_test_name { + -re "received signal SIGINT, Interrupt" { + pass $gdb_test_name + } + } + } + } + } + -re "$gdb_prompt $" { + gdb_assert {$running_count == ($n_threads + 1) * 2} $gdb_test_name + } + } + + gdb_test "detach" "Detaching from.*" + + if {!$interrupted} { + # Now test whether inferior 2's thread were really left + # running. Currently an inline step-over stops all + # threads of all processes. If detach aborts such a step + # over, then threads of other inferiors should be + # re-resumed. Test for that by sending a signal to + # inferior 2. + remote_exec target "kill -SIGUSR1 ${pid_inf2}" + + gdb_test_multiple "" "stop with SIGUSR1" { + -re "received signal SIGUSR1" { + pass $gdb_test_name + } + } + } + + delete_breakpoints + } + } + kill_wait_spawned_process $test_spawn_id +} + +# The test program exits after a while, in case GDB crashes. Make it +# wait at least as long as we may wait before declaring a time out +# failure. +set options { "additional_flags=-DTIMEOUT=$timeout" debug pthreads } + +if {[prepare_for_testing "failed to prepare" $testfile $srcfile $options] == -1} { + return -1 +} + +if ![runto_main] { + return -1 +} + +# Probe support for "set breakpoint condition-evaluation target". +# This setting influences who steps over the breakpoint, the (remote) +# target (e.g. gdbserver) or gdb, thus exposing issues on either the +# target or gdb. +set supports_condition_eval_target 1 +set cmd "set breakpoint condition-evaluation target" +gdb_test_multiple $cmd "probe condition-evaluation target support" { + -re "warning: Target does not support breakpoint condition evaluation.\r\nUsing host evaluation mode instead.\r\n$gdb_prompt $" { + # Target doesn't support breakpoint condition evaluation on + # its side. + set supports_condition_eval_target 0 + pass $gdb_test_name + } + -re "^$cmd\r\n$gdb_prompt $" { + pass $gdb_test_name + } +} + +foreach_with_prefix breakpoint-condition-evaluation {"host" "target"} { + if {!$supports_condition_eval_target && ${breakpoint-condition-evaluation} == "target"} { + continue + } + + foreach_with_prefix target-non-stop {"off" "on"} { + foreach_with_prefix non-stop {"off" "on"} { + if {${non-stop} && !${target-non-stop}} { + # "set non-stop" overrides "maint set + # target-non-stop", no use testing this combination. + continue + } + + foreach_with_prefix displaced {"off" "auto"} { + test ${breakpoint-condition-evaluation} ${target-non-stop} ${non-stop} ${displaced} + } + } + } +}