]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: add '-stopped' and '-running' options to "info threads"
authorTankut Baris Aktemur <tankut.baris.aktemur@intel.com>
Mon, 12 May 2025 07:10:56 +0000 (09:10 +0200)
committerTankut Baris Aktemur <tankut.baris.aktemur@intel.com>
Mon, 12 May 2025 07:11:25 +0000 (09:11 +0200)
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
gdb/NEWS
gdb/doc/gdb.texinfo
gdb/testsuite/gdb.base/options.exp
gdb/testsuite/gdb.threads/info-threads-options.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/info-threads-options.exp [new file with mode: 0644]
gdb/thread.c

index 18a8b7475b44b54c6ab3c857d84e84717e728639..d3699de1653275ad8b8f2c97321ebe494d221b46 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -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
 
index 5e5e888cf38cea33a9d7b4e77039207374ec2d5e..05f550233fe2f2925859f9301b8e24acb75172a7 100644 (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:
 
index 7822e4a0baa7470d975ab36d132d1aa07c055bf2..a0947e2f6f4be3903266575af562bd2faae35795 100644 (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"
diff --git a/gdb/testsuite/gdb.threads/info-threads-options.c b/gdb/testsuite/gdb.threads/info-threads-options.c
new file mode 100644 (file)
index 0000000..2c4cd85
--- /dev/null
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.threads/info-threads-options.exp b/gdb/testsuite/gdb.threads/info-threads-options.exp
new file mode 100644 (file)
index 0000000..38e4e67
--- /dev/null
@@ -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
+    }
+}
index d84d326a8c3b9dd916af9b65ae3aa4954b52a66f..0228027fb928160fde98e338a4eb8498722dd539 100644 (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;