diff --git a/gdb/infrun.c b/gdb/infrun.c index 4c7eb9be7921..c60cfc07aa7e 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -1245,13 +1245,11 @@ follow_exec (ptid_t ptid, const char *exec_file_target) some other thread does the exec, and even if the main thread was stopped or already gone. We may still have non-leader threads of the process on our list. E.g., on targets that don't have thread - exit events (like remote); or on native Linux in non-stop mode if - there were only two threads in the inferior and the non-leader - one is the one that execs (and nothing forces an update of the - thread list up to here). When debugging remotely, it's best to + exit events (like remote) and nothing forces an update of the + thread list up to here. When debugging remotely, it's best to avoid extra traffic, when possible, so avoid syncing the thread list with the target, and instead go ahead and delete all threads - of the process but one that reported the event. Note this must + of the process but the one that reported the event. Note this must be done before calling update_breakpoints_after_exec, as otherwise clearing the threads' resources would reference stale thread breakpoints -- it may have been one of these threads that diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index f73e52f9617e..97d80053c6f6 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -2001,6 +2001,21 @@ linux_handle_extended_wait (struct lwp_info *lp, int status) thread execs, it changes its tid to the tgid, and the old tgid thread might have not been resumed. */ lp->resumed = 1; + + /* All other LWPs are gone now. We'll have received a thread + exit notification for all threads other the execing one. + That one, if it wasn't the leader, just silently changes its + tid to the tgid, and the previous leader vanishes. Since + Linux 3.0, the former thread ID can be retrieved with + PTRACE_GETEVENTMSG, but since we support older kernels, don't + bother with it, and just walk the LWP list. Even with + PTRACE_GETEVENTMSG, we'd still need to lookup the + corresponding LWP object, and it would be an extra ptrace + syscall, so this way may even be more efficient. */ + for (lwp_info *other_lp : all_lwps_safe ()) + if (other_lp != lp && other_lp->ptid.pid () == lp->ptid.pid ()) + exit_lwp (other_lp); + return 0; } diff --git a/gdb/testsuite/gdb.threads/threads-after-exec.c b/gdb/testsuite/gdb.threads/threads-after-exec.c new file mode 100644 index 000000000000..b3ed7ec5f691 --- /dev/null +++ b/gdb/testsuite/gdb.threads/threads-after-exec.c @@ -0,0 +1,56 @@ +/* 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 + +static char *program_name; + +void * +thread_execler (void *arg) +{ + /* Exec ourselves again, but with an extra argument, to avoid + infinite recursion. */ + if (execl (program_name, program_name, "1", NULL) == -1) + { + perror ("execl"); + abort (); + } + + return NULL; +} + +int +main (int argc, char **argv) +{ + pthread_t thread; + + if (argc > 1) + { + /* Getting here via execl. */ + return 0; + } + + program_name = argv[0]; + + pthread_create (&thread, NULL, thread_execler, NULL); + pthread_join (thread, NULL); + + return 0; +} diff --git a/gdb/testsuite/gdb.threads/threads-after-exec.exp b/gdb/testsuite/gdb.threads/threads-after-exec.exp new file mode 100644 index 000000000000..cd8adf900d93 --- /dev/null +++ b/gdb/testsuite/gdb.threads/threads-after-exec.exp @@ -0,0 +1,57 @@ +# 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 . + +# Test that after an exec of a non-leader thread, we don't leave the +# non-leader thread listed in internal thread lists, causing problems. + +standard_testfile .c + +proc do_test { } { + if [prepare_for_testing "failed to prepare" $::testfile $::srcfile {debug pthreads}] { + return -1 + } + + if ![runto_main] { + return + } + + gdb_test "catch exec" "Catchpoint $::decimal \\(exec\\)" + + gdb_test "continue" "Catchpoint $::decimal .*" "continue until exec" + + # Confirm we only have one thread in the thread list. + gdb_test "info threads" "\\* 1\[ \t\]+\[^\r\n\]+.*" + + if {[istarget *-*-linux*] && [gdb_is_target_native]} { + # Confirm there's only one LWP in the list as well, and that + # it is bound to thread 1.1. + set inf_pid [get_inferior_pid] + gdb_test_multiple "maint info linux-lwps" "" { + -wrap -re "Thread ID *\r\n$inf_pid\.$inf_pid\.0\[ \t\]+1\.1 *" { + pass $gdb_test_name + } + } + } + + # Test that GDB is able to kill the inferior. This used to crash + # on native Linux as GDB did not dispose of the pre-exec LWP for + # the non-leader (and that LWP did not have a matching thread in + # the core thread list). + gdb_test "with confirm off -- kill" \ + "\\\[Inferior 1 (.*) killed\\\]" \ + "kill inferior" +} + +do_test