]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
Allow DAP "threads" request when inferior is running
authorTom Tromey <tromey@adacore.com>
Thu, 12 Jun 2025 16:48:25 +0000 (10:48 -0600)
committerTom Tromey <tromey@adacore.com>
Tue, 24 Jun 2025 14:38:09 +0000 (08:38 -0600)
A user pointed out that DAP allows the "threads" request to work when
the inferior is running.  This is documented in the overview, not the
specification.

While looking into this, I found a few other issues:

* The _thread_name function was not marked @in_gdb_thread.
  This isn't very important but is still an oversight.

* DAP requires all threads to have a name -- the field is not optional
  in the "Thread" type.

* There was no test examining events resulting from the inferior
  printing to stdout.

This patch fixes all these problems.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33080

gdb/python/lib/gdb/dap/threads.py
gdb/testsuite/gdb.dap/threads.c [new file with mode: 0644]
gdb/testsuite/gdb.dap/threads.exp [new file with mode: 0644]

index c271961a407206c4b90783313f5a227150e6ae7f..89046a8b1b3dbedd5b6c8b86dcb37799a203c19b 100644 (file)
 import gdb
 
 from .server import request
+from .startup import in_gdb_thread
 
 
+@in_gdb_thread
 def _thread_name(thr):
     if thr.name is not None:
         return thr.name
     if thr.details is not None:
         return thr.details
-    return None
+    # Always return a name, as the protocol doesn't allow for nameless
+    # threads.  Use the local thread number here... it doesn't matter
+    # without multi-inferior but in that case it might make more
+    # sense.
+    return f"Thread #{thr.num}"
 
 
-@request("threads")
+@request("threads", expect_stopped=False)
 def threads(**args):
     result = []
     for thr in gdb.selected_inferior().threads():
-        one_result = {
-            "id": thr.global_num,
-        }
-        name = _thread_name(thr)
-        if name is not None:
-            one_result["name"] = name
-        result.append(one_result)
+        result.append(
+            {
+                "id": thr.global_num,
+                "name": _thread_name(thr),
+            }
+        )
     return {
         "threads": result,
     }
diff --git a/gdb/testsuite/gdb.dap/threads.c b/gdb/testsuite/gdb.dap/threads.c
new file mode 100644 (file)
index 0000000..168f044
--- /dev/null
@@ -0,0 +1,67 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2019-2025 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+#define NUM 2
+
+static pthread_barrier_t threads_started_barrier;
+
+static void *
+thread_function (void *arg)
+{
+  pthread_barrier_wait (&threads_started_barrier);
+
+  while (1)
+    sleep (1);
+
+  pthread_exit (NULL);
+}
+
+static void
+all_started (void)
+{
+}
+
+int
+main ()
+{
+  pthread_t threads[NUM];
+  long i;
+
+  pthread_barrier_init (&threads_started_barrier, NULL, NUM + 1);
+
+  for (i = 1; i <= NUM; i++)
+    {
+      int res;
+
+      res = pthread_create (&threads[i - 1], NULL, thread_function, NULL);
+    }
+
+  pthread_barrier_wait (&threads_started_barrier);
+
+  all_started ();
+
+  printf ("sleeping\n");
+  fflush (stdout);
+  sleep (180);
+
+  exit (EXIT_SUCCESS);
+}
diff --git a/gdb/testsuite/gdb.dap/threads.exp b/gdb/testsuite/gdb.dap/threads.exp
new file mode 100644 (file)
index 0000000..c91d107
--- /dev/null
@@ -0,0 +1,81 @@
+# Copyright 2025 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 DAP "threads" request.
+
+require allow_shlib_tests allow_dap_tests
+
+load_lib dap-support.exp
+
+standard_testfile
+
+set libname $testfile-solib
+set srcfile_lib $srcdir/$subdir/$libname.c
+set binfile_lib [standard_output_file $libname.so]
+
+if {[build_executable "failed to prepare" $testfile $srcfile \
+        {debug pthreads}] == -1} {
+    return
+}
+
+if {[dap_initialize] == ""} {
+    return
+}
+
+set launch_id [dap_launch $testfile]
+
+set obj [dap_check_request_and_response "set breakpoint on all_started function" \
+            setFunctionBreakpoints \
+            {o breakpoints [a [o name [s all_started]]]}]
+set fn_bpno [dap_get_breakpoint_number $obj]
+
+dap_check_request_and_response "configurationDone" configurationDone
+
+dap_check_response "launch response" launch $launch_id
+
+lassign [dap_wait_for_event_and_check "stopped at function breakpoint" \
+            stopped \
+            "body reason" breakpoint \
+            "body hitBreakpointIds" $fn_bpno] \
+    ignore \
+    all_events
+
+# Verify that we saw the correct number of thread events.
+set count 0
+foreach event $all_events {
+    if {[dict get $event type] == "event"
+       && [dict get $event event] == "thread"
+       && [dict get $event body reason] == "started"} {
+       incr count
+    }
+}
+gdb_assert {$count == 3} "correct number of thread events"
+
+dap_check_request_and_response "continue" continue \
+    {o threadId [i 1]}
+
+# Make sure that the inferior has really re-started -- note that there
+# is no "continue" event, because the "continue" request suppresses
+# those.
+dap_wait_for_event_and_check "output from inferior" output \
+    {body output} "sleeping\\n"
+
+lassign [dap_check_request_and_response "threads request" threads] \
+    response ignore
+
+gdb_assert {[llength [dict get $response body threads]] == 3} \
+    "correct number of threads"
+
+dap_shutdown true