diff --git a/gdb/testsuite/gdb.threads/next-fork-exec-other-thread.c b/gdb/testsuite/gdb.threads/next-fork-exec-other-thread.c new file mode 100644 index 00000000000..884706c6c3c --- /dev/null +++ b/gdb/testsuite/gdb.threads/next-fork-exec-other-thread.c @@ -0,0 +1,82 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2023 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 . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_LOOP_ITER 10000 + +static char *argv0; + +static void* +worker_a (void *pArg) +{ + int iter = 0; + char *args[] = {argv0, "self-call", NULL }; + + while (iter++ < MAX_LOOP_ITER) + { + pid_t pid = FORK_FUNC (); + if (pid == 0) + { + /* child */ + if (execvp (args[0], args) == -1) + { + fprintf (stderr, "execvp error: %d\n", errno); + exit (1); + } + } + + waitpid (pid, NULL, 0); + usleep (5); + } +} + +static void* +worker_b (void *pArg) +{ + int iter = 0; + while (iter++ < MAX_LOOP_ITER) /* for loop */ + { + usleep (5); /* break here */ + usleep (5); /* other line */ + } +} + +int +main (int argc, char **argv) +{ + pthread_t wa_pid; + pthread_t wb_pid; + + argv0 = argv[0]; + + if (argc > 1 && strcmp (argv[1], "self-call") == 0) + exit (0); + + pthread_create (&wa_pid, NULL, worker_a, NULL); + pthread_create (&wb_pid, NULL, worker_b, NULL); + pthread_join (wa_pid, NULL); + + exit (0); +} diff --git a/gdb/testsuite/gdb.threads/next-fork-exec-other-thread.exp b/gdb/testsuite/gdb.threads/next-fork-exec-other-thread.exp new file mode 100644 index 00000000000..5e0ac5b78df --- /dev/null +++ b/gdb/testsuite/gdb.threads/next-fork-exec-other-thread.exp @@ -0,0 +1,131 @@ +# Copyright 2022-2023 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 . + +# This test was adapted from next-fork-other-thread.exp. The .c file +# was adapted from the reproducer for this bug: +# +# https://sourceware.org/bugzilla/show_bug.cgi?id=30387# +# +# That bug demonstrates a problem with software-singlestep in gdbserver. +# Prior to being fixed, this test also demonstrated that bug for a +# 32-bit ARM target. (Use RUNTESTFLAGS="--target_board=native-gdbserver".) +# It has been reproduced on a Raspberry Pi running Ubunutu server +# 20.04.5 LTS with 32-bit kernel + 32-bit userland. It was NOT reproducible +# using a circa 2023 Raspberry Pi OS w/ 64-bit kernel and 32-bit userland. + +standard_testfile + +# Line where to stop the main thread. +set break_here_line [gdb_get_line_number "break here"] + +# Build executables, one for each fork flavor. +foreach_with_prefix fork_func {fork vfork} { + set opts [list debug pthreads additional_flags=-DFORK_FUNC=${fork_func}] + if { [build_executable "failed to prepare" \ + ${testfile}-${fork_func} ${srcfile} $opts] } { + return + } +} + +# If testing against GDBserver, consume all it its output. + +proc drain_gdbserver_output { } { + if { [info exists ::server_spawn_id] } { + gdb_test_multiple "" "" { + -i "$::server_spawn_id" + -timeout 0 + -re ".+" { + exp_continue + } + } + } +} + +# Run the test with the given parameters: +# +# - FORK_FUNC: fork flavor, "fork" or "vfork". +# - TARGET-NON-STOP: "maintenance set target-non-stop" value, "auto", "on" or +# "off". +# - NON-STOP: "set non-stop" value, "on" or "off". +# - DISPLACED-STEPPING: "set displaced-stepping" value, "auto", "on" or "off". + +proc do_test { fork_func target-non-stop non-stop displaced-stepping } { + save_vars { ::GDBFLAGS } { + append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\"" + append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\"" + clean_restart ${::binfile}-${fork_func} + } + + gdb_test_no_output "set displaced-stepping ${displaced-stepping}" + + if { ![runto_main] } { + return + } + + # The "Detached after (v)fork" messages get in the way in non-stop, disable + # them. + gdb_test_no_output "set print inferior-events off" + + # Advance the next-ing thread to the point where we'll execute the nexts. + # Leave the breakpoint in: it will force GDB to step over it while next-ing, + # which exercises some additional code paths. + gdb_test "break $::break_here_line" "Breakpoint $::decimal at $::hex.*" + gdb_test "continue" "hit Breakpoint $::decimal, worker_b.*" + + # Next an arbitrary number of times over the lines of the loop. + for { set i 0 } { $i < 30 } { incr i } { + # If testing against GDBserver, the forking threads cause a lot of + # "Detaching from process XYZ" messages to appear. If we don't consume + # that output, GDBserver eventually blocks on a full stderr. Drain it + # once every loop. It may not be needed for 20 iterations, but it's + # needed if you increase to 200 iterations. + drain_gdbserver_output + + with_test_prefix "i=$i" { + if { [gdb_test "next" "other line.*" "next to other line"] != 0 } { + return + } + + if { [gdb_test "next" "for loop.*" "next to for loop"] != 0 } { + return + } + + if { [gdb_test "next" "break here.*" "next to break here"] != 0} { + return + } + } + } +} + +foreach_with_prefix fork_func {fork vfork} { + foreach_with_prefix target-non-stop {auto on off} { + # This file was copied from next-fork-other-thread.exp and + # then adapted for the a case which also involves an exec in + # addition to the fork. Ideally, we should test non-stop "on" + # in addition to "off", but, for this test, that results in a + # number of failures occur preceded by the message: + # + # Cannot execute this command while the selected thread is running. + # + # That seems like correct behavior to me, but perhaps the + # non-stop case can be made to work; if so, simply add "on" + # after "off" on the line below... + foreach_with_prefix non-stop {off} { + foreach_with_prefix displaced-stepping {auto on off} { + do_test ${fork_func} ${target-non-stop} ${non-stop} ${displaced-stepping} + } + } + } +} diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc index 651f219b738..e1806ade82f 100644 --- a/gdbserver/linux-low.cc +++ b/gdbserver/linux-low.cc @@ -2463,7 +2463,12 @@ linux_process_target::resume_stopped_resumed_lwps (thread_info *thread) int step = 0; if (thread->last_resume_kind == resume_step) - step = maybe_hw_step (thread); + { + if (supports_software_single_step ()) + install_software_single_step_breakpoints (lp); + + step = maybe_hw_step (thread); + } threads_debug_printf ("resuming stopped-resumed LWP %s at %s: step=%d", target_pid_to_str (ptid_of (thread)).c_str (),