gdb: add '-stopped' and '-running' options to "info threads"

Add two options to "info threads": `-stopped` and `-running`.

The purpose of these options is to filter the output of the command.
The `-stopped` option means "print stopped threads only" and,
similarly, `-running` means "print the running threads only".  When
both options are provided by the user, the indication is that the user
wants the union.  That is, the output contains both stopped and
running threads.

Suppose we have an application with 5 threads, 2 of which have hit a
breakpoint.  The "info threads" command in the non-stop mode gives:

  (gdb) info threads
    Id   Target Id             Frame
  * 1    Thread 0x7ffff7d99740 (running)
    2    Thread 0x7ffff7d98700 something () at file.c:30
    3    Thread 0x7ffff7597700 (running)
    4    Thread 0x7ffff6d96700 something () at file.c:30
    5    Thread 0x7ffff6595700 (running)
  (gdb)

Using the "-stopped" flag, we get

  (gdb) info threads -stopped
    Id   Target Id             Frame
    2    Thread 0x7ffff7d98700 something () at file.c:30
    4    Thread 0x7ffff6d96700 something () at file.c:30
  (gdb)

Using the "-running" flag, we get

  (gdb) info threads -running
    Id   Target Id             Frame
  * 1    Thread 0x7ffff7d99740 (running)
    3    Thread 0x7ffff7597700 (running)
    5    Thread 0x7ffff6595700 (running)
  (gdb)

Using both flags prints all:

  (gdb) info threads -stopped -running
    Id   Target Id             Frame
  * 1    Thread 0x7ffff7d99740 (running)
    2    Thread 0x7ffff7d98700 something () at file.c:30
    3    Thread 0x7ffff7597700 (running)
    4    Thread 0x7ffff6d96700 something () at file.c:30
    5    Thread 0x7ffff6595700 (running)
  (gdb)

When combined with a thread ID, filtering applies to those threads that
are matched by the ID.

  (gdb) info threads 3
    Id   Target Id             Frame
    3    Thread 0x7ffff7597700 (running)
  (gdb) info threads -stopped 3
  No threads matched.
  (gdb)

Regression-tested on X86_64 Linux.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Reviewed-By: Guinevere Larsen <guinevere@redhat.com>
Approved-by: Pedro Alves <pedro@palves.net
This commit is contained in:
Tankut Baris Aktemur 2025-05-12 09:10:56 +02:00
parent 0ea1e5df96
commit be437614a1
6 changed files with 272 additions and 14 deletions

View file

@ -90,12 +90,17 @@ info sharedlibrary
command are now for the full memory range allocated to the shared
library.
info threads [-gid] [ID]...
If no threads match the given ID(s), GDB now prints
info threads [-gid] [-stopped] [-running] [ID]...
If no threads match the given ID(s) or filter options, GDB now prints
No threads matched.
without printing the provided argument.
without printing the provided arguments. The newly added '-stopped'
option makes GDB list the stopped threads only. Similarly,
'-running' makes GDB list the running threads only. If both options
are given together, both stopped and running threads are listed.
These new flags can be useful to get a reduced list when there is a
large number of threads.
* GDB-internal Thread Local Storage (TLS) support

View file

@ -3807,7 +3807,7 @@ Thread 1 "main" received signal SIGINT, Interrupt.
@table @code
@anchor{info_threads}
@kindex info threads
@item info threads @r{[}-gid@r{]} @r{[}@var{thread-id-list}@r{]}
@item info threads @r{[}-gid@r{]} @r{[}-stopped@r{]} @r{[}-running@r{]} @r{[}@var{thread-id-list}@r{]}
Display information about one or more threads. With no arguments
displays information about all threads. You can specify the list of
@ -3857,6 +3857,14 @@ If you're debugging multiple inferiors, @value{GDBN} displays thread
IDs using the qualified @var{inferior-num}.@var{thread-num} format.
Otherwise, only @var{thread-num} is shown.
If you specify the @samp{-stopped} option, @value{GDBN} filters the
output of the command to print the stopped threads only. Similarly,
if you specify the @samp{-running} option, @value{GDBN} filters the
output to print the running threads only. These options can be
helpful to reduce the output list if there is a large number of
threads. If you specify both options, @value{GDBN} prints both
stopped and running threads.
If you specify the @samp{-gid} option, @value{GDBN} displays a column
indicating each thread's global thread ID:

View file

@ -508,12 +508,26 @@ proc_with_prefix test-thread-apply {} {
proc_with_prefix test-info-threads {} {
test_gdb_complete_multiple "info threads " "" "" {
"-gid"
"-running"
"-stopped"
"ID"
}
test_gdb_complete_multiple "info threads " "-" "" {
"-gid"
"-running"
"-stopped"
}
test_gdb_complete_unique \
"info threads -" \
"info threads -g" \
"info threads -gid"
test_gdb_complete_unique \
"info threads -r" \
"info threads -running"
test_gdb_complete_unique \
"info threads -s" \
"info threads -stopped"
# "ID" isn't really something the user can type.
test_gdb_complete_none "info threads I"

View file

@ -0,0 +1,77 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2022-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 4
static pthread_barrier_t threads_started_barrier;
static void
stop_here ()
{
}
static void
spin ()
{
while (1)
usleep (1);
}
static void *
work (void *arg)
{
int id = *(int *) arg;
pthread_barrier_wait (&threads_started_barrier);
if (id % 2 == 0)
stop_here ();
else
spin ();
pthread_exit (NULL);
}
int
main ()
{
/* Ensure we stop if GDB crashes and DejaGNU fails to kill us. */
alarm (10);
pthread_t threads[NUM];
int ids[NUM];
pthread_barrier_init (&threads_started_barrier, NULL, NUM + 1);
for (int i = 0; i < NUM; i++)
{
ids[i] = i;
pthread_create (&threads[i], NULL, work, &ids[i]);
}
/* Wait until all threads are seen running. */
pthread_barrier_wait (&threads_started_barrier);
stop_here ();
return 0;
}

View file

@ -0,0 +1,131 @@
# Copyright (C) 2022-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 the filter flags of the "info threads" command.
standard_testfile
if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \
executable debug] != "" } {
return -1
}
save_vars { GDBFLAGS } {
append GDBFLAGS " -ex \"set non-stop on\""
clean_restart $binfile
}
if ![runto_main] {
return -1
}
gdb_breakpoint "stop_here"
gdb_test_multiple "continue -a&" "" {
-re "Continuing.\r\n$gdb_prompt " {
pass $gdb_test_name
}
}
set expected_hits 3
set fill "\[^\r\n\]+"
set num_hits 0
gdb_test_multiple "" "hit the breakpoint" -lbl {
-re "\r\nThread ${fill} hit Breakpoint ${decimal}," {
incr num_hits
if {$num_hits < $expected_hits} {
exp_continue
}
}
}
gdb_assert {$num_hits == $expected_hits} "expected threads hit the bp"
# Count the number of running/stopped threads reported
# by the "info threads" command. We also capture thread ids
# for additional tests.
set running_tid "invalid"
set stopped_tid "invalid"
set eol "(?=\r\n)"
foreach_with_prefix flag {"" "-running" "-stopped" "-running -stopped"} {
set num_running 0
set num_stopped 0
gdb_test_multiple "info threads $flag" "info threads $flag" -lbl {
-re "Id${fill}Target Id${fill}Frame${fill}${eol}" {
exp_continue
}
-re "^\r\n. (${decimal})${fill}Thread ${fill}.running.${eol}" {
incr num_running
set running_tid $expect_out(1,string)
exp_continue
}
-re "^\r\n. (${decimal})${fill}Thread ${fill}stop_here ${fill}${eol}" {
incr num_stopped
set stopped_tid $expect_out(1,string)
exp_continue
}
-re "^\r\n$gdb_prompt $" {
pass $gdb_test_name
}
}
if {$flag eq "-running"} {
gdb_assert {$num_running == 2} "num running"
gdb_assert {$num_stopped == 0} "num stopped"
} elseif {$flag eq "-stopped"} {
gdb_assert {$num_running == 0} "num running"
gdb_assert {$num_stopped == 3} "num stopped"
} else {
gdb_assert {$num_running == 2} "num running"
gdb_assert {$num_stopped == 3} "num stopped"
}
}
verbose -log "running_tid=$running_tid, stopped_tid=$stopped_tid"
# Test specifying thread ids.
gdb_test "info threads -running $stopped_tid" \
"No threads matched\\." \
"info thread -running for a stopped thread"
gdb_test "info threads -stopped $running_tid" \
"No threads matched\\." \
"info thread -stopped for a running thread"
set ws "\[ \t\]+"
foreach tid "\"$running_tid\" \"$running_tid $stopped_tid\"" {
gdb_test "info threads -running $tid" \
[multi_line \
"${ws}Id${ws}Target Id${ws}Frame${ws}" \
"${ws}${running_tid}${ws}Thread ${fill}.running."] \
"info thread -running with [llength $tid] thread ids"
}
foreach tid "\"$stopped_tid\" \"$stopped_tid $running_tid\"" {
gdb_test "info threads -stopped $tid" \
[multi_line \
"${ws}Id${ws}Target Id${ws}Frame${ws}" \
"${ws}${stopped_tid}${ws}Thread ${fill} stop_here ${fill}"] \
"info thread -stopped with [llength $tid] thread ids"
}
gdb_test_multiple "info threads -stopped -running $stopped_tid $running_tid" \
"filter flags and tids combined" {
-re -wrap ".*stop_here.*running.*" {
pass $gdb_test_name
}
-re -wrap ".*running.*stop_here.*" {
pass $gdb_test_name
}
}

View file

@ -1044,6 +1044,10 @@ struct info_threads_opts
{
/* For "-gid". */
bool show_global_ids = false;
/* For "-running". */
bool show_running_threads = false;
/* For "-stopped". */
bool show_stopped_threads = false;
};
static const gdb::option::option_def info_threads_option_defs[] = {
@ -1053,7 +1057,16 @@ static const gdb::option::option_def info_threads_option_defs[] = {
[] (info_threads_opts *opts) { return &opts->show_global_ids; },
N_("Show global thread IDs."),
},
gdb::option::flag_option_def<info_threads_opts> {
"running",
[] (info_threads_opts *opts) { return &opts->show_running_threads; },
N_("Show running threads only."),
},
gdb::option::flag_option_def<info_threads_opts> {
"stopped",
[] (info_threads_opts *opts) { return &opts->show_stopped_threads; },
N_("Show stopped threads only."),
},
};
/* Helper for print_thread_info. Returns true if THR should be
@ -1095,7 +1108,17 @@ should_print_thread (const char *requested_threads,
if (thr->state == THREAD_EXITED)
return false;
return true;
bool is_stopped = (thr->state == THREAD_STOPPED);
if (opts.show_stopped_threads && is_stopped)
return true;
bool is_running = (thr->state == THREAD_RUNNING);
if (opts.show_running_threads && is_running)
return true;
/* If the user did not pass a filter flag, show the thread. */
return (!opts.show_stopped_threads
&& !opts.show_running_threads);
}
/* Return the string to display in "info threads"'s "Target Id"
@ -1254,13 +1277,15 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
list_emitter.emplace (uiout, "threads");
else
{
int n_threads = 0;
int n_matching_threads = 0;
/* The width of the "Target Id" column. Grown below to
accommodate the largest entry. */
size_t target_id_col_width = 17;
for (thread_info *tp : all_threads ())
{
any_thread = true;
/* In case REQUESTED_THREADS contains $_thread. */
if (current_thread != nullptr)
switch_to_thread (current_thread);
@ -1277,12 +1302,12 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
= std::max (target_id_col_width,
thread_target_id_str (tp).size ());
++n_threads;
++n_matching_threads;
}
if (n_threads == 0)
if (n_matching_threads == 0)
{
if (requested_threads == NULL || *requested_threads == '\0')
if (!any_thread)
uiout->message (_("No threads.\n"));
else
uiout->message (_("No threads matched.\n"));
@ -1290,7 +1315,7 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
}
table_emitter.emplace (uiout, opts.show_global_ids ? 5 : 4,
n_threads, "threads");
n_matching_threads, "threads");
uiout->table_header (1, ui_left, "current", "");
uiout->table_header (4, ui_left, "id-in-tg", "Id");
@ -1305,8 +1330,6 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
for (inferior *inf : all_inferiors ())
for (thread_info *tp : inf->threads ())
{
any_thread = true;
if (tp == current_thread && tp->state == THREAD_EXITED)
current_exited = true;