aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/infrun.c8
-rw-r--r--gdb/linux-nat.c15
-rw-r--r--gdb/testsuite/gdb.threads/threads-after-exec.c56
-rw-r--r--gdb/testsuite/gdb.threads/threads-after-exec.exp57
4 files changed, 131 insertions, 5 deletions
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 4c7eb9b..c60cfc0 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 f73e52f..97d8005 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 0000000..b3ed7ec
--- /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 <http://www.gnu.org/licenses/>. */
+
+#include <pthread.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+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 0000000..cd8adf9
--- /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 <http://www.gnu.org/licenses/>.
+
+# 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