diff options
author | Tom Tromey <tromey@adacore.com> | 2025-06-12 10:48:25 -0600 |
---|---|---|
committer | Tom Tromey <tromey@adacore.com> | 2025-06-24 08:38:09 -0600 |
commit | 744dabeb29d6fb07f3fe7d91031adc5c85b7f865 (patch) | |
tree | 6b556869cf50921f9d1593ed72d0b55570f553ba /gdb | |
parent | baba7c9a2ae10eb19c8eff6f5c981268b1f3831e (diff) | |
download | binutils-744dabeb29d6fb07f3fe7d91031adc5c85b7f865.zip binutils-744dabeb29d6fb07f3fe7d91031adc5c85b7f865.tar.gz binutils-744dabeb29d6fb07f3fe7d91031adc5c85b7f865.tar.bz2 |
Allow DAP "threads" request when inferior is running
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
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/python/lib/gdb/dap/threads.py | 23 | ||||
-rw-r--r-- | gdb/testsuite/gdb.dap/threads.c | 67 | ||||
-rw-r--r-- | gdb/testsuite/gdb.dap/threads.exp | 81 |
3 files changed, 162 insertions, 9 deletions
diff --git a/gdb/python/lib/gdb/dap/threads.py b/gdb/python/lib/gdb/dap/threads.py index c271961..89046a8 100644 --- a/gdb/python/lib/gdb/dap/threads.py +++ b/gdb/python/lib/gdb/dap/threads.py @@ -16,27 +16,32 @@ 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 index 0000000..168f044 --- /dev/null +++ b/gdb/testsuite/gdb.dap/threads.c @@ -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 index 0000000..c91d107 --- /dev/null +++ b/gdb/testsuite/gdb.dap/threads.exp @@ -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 |