diff mbox

thread ID ranges (Per-inferior thread IDs)

Message ID 567C0779.909@redhat.com
State New
Headers show

Commit Message

Pedro Alves Dec. 24, 2015, 2:55 p.m. UTC
On 12/18/2015 06:11 PM, Pedro Alves wrote:
>>> >> +@var{thread-id}.  It can be a single thread ID, as shown in the first
>>> >> +field of the @samp{info threads} display, with or without an inferior
>>> >> +qualifier (e.g., @samp{2.1} or @samp{1}); or it could be a range of
>>> >> +thread numbers, as in @code{2-4}.  To apply a command to all threads
>>> >> +in descending order, type @kbd{thread apply all @var{command}}.  To
>>> >> +apply a command to all threads in ascending order, type @kbd{thread
>>> >> +apply all -ascending @var{command}}.
>> > 
>> > Can I use a range of qualified IDs, as in "2.1-2.4"?
> I was thinking of adding support for ranges of threads in the "2.1-4"
> form, that is, I didn't think it'd make sense to cross inferior,
> like "2.1-3.3", but I haven't implemented it yet.
> 

I did this now.  See the patch below, which applies on top of the
previous one (so you can easily see the new additions).
I expect to squash it all into one patch and resubmit later on.

(I also found a couple other "threadnum" references I had somehow
misssed previously.)

I (force) pushed this to the users/palves/thread-ids-per-inferior
branch for testing convenience.

From 2ff6a3cfdab76841f54ce657af381e6139b0a2df Mon Sep 17 00:00:00 2001
From: Pedro Alves <palves@redhat.com>
Date: Thu, 24 Dec 2015 14:37:39 +0000
Subject: [PATCH] thread ID lists and ranges

---
 gdb/doc/gdb.texinfo                           |  56 ++++---
 gdb/gdbthread.h                               |  91 +++++++++++
 gdb/testsuite/gdb.multi/per-inferior-tids.c   |  20 ++-
 gdb/testsuite/gdb.multi/per-inferior-tids.exp | 135 ++++++++++++----
 gdb/testsuite/gdb.threads/pthreads.exp        |  43 ++++++
 gdb/testsuite/gdb.threads/thread-find.exp     |   4 +-
 gdb/thread.c                                  | 215 ++++++++++++++++++++++----
 7 files changed, 482 insertions(+), 82 deletions(-)

Comments

Eli Zaretskii Dec. 24, 2015, 4:25 p.m. UTC | #1
> Date: Thu, 24 Dec 2015 14:55:53 +0000
> From: Pedro Alves <palves@redhat.com>
> CC: gdb-patches@sourceware.org
> 
> I did this now.  See the patch below, which applies on top of the
> previous one (so you can easily see the new additions).
> I expect to squash it all into one patch and resubmit later on.
> 
> (I also found a couple other "threadnum" references I had somehow
> misssed previously.)

Thanks, the documentation part looks good to me.
diff mbox

Patch

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 071212e..06981c3 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -2909,6 +2909,20 @@  Until you create a second inferior, @value{GDBN} does not show the
 the full @var{inferior-num}.@var{thread-num} form to refer to threads
 of inferior 1, the initial inferior.
 
+@anchor{thread ID lists}
+@cindex thread ID lists
+Some commands accept a space-separated @dfn{thread ID list} as
+argument.  A list element can be a thread ID as shown in the first
+field of the @samp{info threads} display, with or without an inferior
+qualifier (e.g., @samp{2.1} or @samp{1}); or can be a range of thread
+numbers, again with or without an inferior qualifier, as in
+@var{inf1}.@var{thr1}-@var{thr2} or @var{thr1}-@var{thr2} (e.g.,
+@samp{1.2-4} or @samp{2-4}).  For example, if the current inferior is
+1, the thread list @samp{1 2-3 4.5 6.7-9} includes threads 1 to 3 of
+inferior 1, thread 5 of inferior 4 and threads 7 to 9 of inferior 6.
+That is, in expanded qualified form, the same as @samp{1.1 1.2 1.3 4.5
+6.7 6.8 6.9}.
+
 @anchor{global thread numbers}
 @cindex global thread number
 @cindex global thread identifier (GDB)
@@ -2931,10 +2945,13 @@  general information on convenience variables.
 
 @table @code
 @kindex info threads
-@item info threads @r{[}@var{id}@dots{}@r{]}
-Display a summary of all threads currently in your program.  Optional 
-argument @var{id}@dots{} is one or more thread ids separated by spaces, and
-means to print information only about the specified thread or threads.
+@item info threads @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
+threads that you want to display using the thread ID list syntax
+(@pxref{thread ID lists}).
+
 @value{GDBN} displays for each thread (in this order):
 
 @enumerate
@@ -3025,17 +3042,14 @@  threads.
 
 @kindex thread apply
 @cindex apply command to several threads
-@item thread apply [@var{thread-id} | all [-ascending]] @var{command}
+@item thread apply [@var{thread-id-list} | all [-ascending]] @var{command}
 The @code{thread apply} command allows you to apply the named
-@var{command} to one or more threads.  Specify the numbers of the
-threads that you want affected with the command argument
-@var{thread-id}.  It can be a single thread ID, as shown in the first
-field of the @samp{info threads} display, with or without an inferior
-qualifier (e.g., @samp{2.1} or @samp{1}); or it could be a range of
-thread numbers, as in @code{2-4}.  To apply a command to all threads
-in descending order, type @kbd{thread apply all @var{command}}.  To
-apply a command to all threads in ascending order, type @kbd{thread
-apply all -ascending @var{command}}.
+@var{command} to one or more threads.  Specify the threads that you
+want affected using the thread ID list syntax (@pxref{thread ID
+lists}), or specify @code{all} to apply to all threads.  To apply a
+command to all threads in descending order, type @kbd{thread apply all
+@var{command}}.  To apply a command to all threads in ascending order,
+type @kbd{thread apply all -ascending @var{command}}.
 
 
 @kindex thread name
@@ -4033,7 +4047,7 @@  slow down the running of your program.
 
 @table @code
 @kindex watch
-@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{threadnum}@r{]} @r{[}mask @var{maskvalue}@r{]}
+@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
 Set a watchpoint for an expression.  @value{GDBN} will break when the
 expression @var{expr} is written into by the program and its value
 changes.  The simplest (and the most popular) use of this command is
@@ -4043,9 +4057,9 @@  to watch the value of a single variable:
 (@value{GDBP}) watch foo
 @end smallexample
 
-If the command includes a @code{@r{[}thread @var{threadnum}@r{]}}
+If the command includes a @code{@r{[}thread @var{thread-id}@r{]}}
 argument, @value{GDBN} breaks only when the thread identified by
-@var{threadnum} changes the value of @var{expr}.  If any other threads
+@var{thread-id} changes the value of @var{expr}.  If any other threads
 change the value of @var{expr}, @value{GDBN} will not break.  Note
 that watchpoints restricted to a single thread in this way only work
 with Hardware Watchpoints.
@@ -4077,12 +4091,12 @@  Examples:
 @end smallexample
 
 @kindex rwatch
-@item rwatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{threadnum}@r{]} @r{[}mask @var{maskvalue}@r{]}
+@item rwatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
 Set a watchpoint that will break when the value of @var{expr} is read
 by the program.
 
 @kindex awatch
-@item awatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{threadnum}@r{]} @r{[}mask @var{maskvalue}@r{]}
+@item awatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
 Set a watchpoint that will break when @var{expr} is either read from
 or written into by the program.
 
@@ -27781,10 +27795,10 @@  current-thread-id="1",number-of-threads="3"
 @subsubheading Synopsis
 
 @smallexample
- -thread-select @var{threadnum}
+ -thread-select @var{thread-id}
 @end smallexample
 
-Make thread with global thread number @var{threadnum} the current
+Make thread with global thread number @var{thread-id} the current
 thread.  It prints the number of the new current thread, and the
 topmost frame for that thread.
 
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 99fb03f..35911d7 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -30,6 +30,7 @@  struct symtab;
 #include "btrace.h"
 #include "common/vec.h"
 #include "target/waitstatus.h"
+#include "cli/cli-utils.h"
 
 /* Frontend view of the thread state.  Possible extensions: stepping,
    finishing, until(ling),...  */
@@ -410,6 +411,96 @@  extern int valid_global_thread_id (int thread);
    thrown.  */
 struct thread_info *parse_thread_id (const char *tidstr, const char **end);
 
+/* The possible states of the tid range parser's state machine.  */
+enum tid_range_state
+{
+  /* Parsing the inferior number.  */
+  TID_RANGE_STATE_INFERIOR,
+
+  /* Parsing the thread number or thread number range.  */
+  TID_RANGE_STATE_THREAD_RANGE,
+};
+
+/* An object of this type is passed to tid_range_parser_get_tid.  It
+   must be initialized by calling tid_range_parser_init.  This type is
+   defined here so that it can be stack-allocated, but all members
+   should be treated as opaque.  */
+struct tid_range_parser
+{
+  /* What sub-component are we expecting.  */
+  enum tid_range_state state;
+
+  /* The string being parsed.  When parsing has finished, this points
+     past the last parsed token.  */
+  const char *string;
+
+  /* The range parser state when we're parsing the thread number
+     sub-component.  */
+  struct get_number_or_range_state range_parser;
+
+  /* Last inferior number returned.  */
+  int inf_num;
+
+  /* True if the TID last parsed was explicitly inferior-qualified.
+     IOW, whether the spec specified an inferior number
+     explicitly.  */
+  int qualified;
+
+  /* The inferior number to assume if the TID is not qualified.  */
+  int default_inferior;
+};
+
+/* Initialize a tid_range_parser for use with
+   tid_range_parser_get_tid.  TIDLIST is the string to be parsed.
+   DEFAULT_INFERIOR is the inferior number to assume if a
+   non-qualified thread ID is found.  */
+extern void tid_range_parser_init (struct tid_range_parser *parser,
+				   const char *tidlist,
+				   int default_inferior);
+
+/* Parse a thread ID or a thread range list.
+
+   A range will be of the form
+   <inferior_num>.<thread_number1>-<thread_number1> and will represent
+   all the threads of inferior inferior_num with number between
+   thread_number1 and thread_number2, inclusive.  <inferior_num> can
+   also be omitted, as in <thread_number1>-<thread_number1>, in which
+   case GDB infers the inferior number from the current inferior.
+
+   While processing a thread ID range list, this function is called
+   iteratively; At each call it will return (in the INF_NUM and
+   THR_NUM output parameters) the next thread in the range.
+
+   At the beginning of parsing a thread range, the char pointer
+   PARSER->string will be advanced past <thread_number1> and left
+   pointing at the '-' token.  Subsequent calls will not advance the
+   pointer until the range is completed.  The call that completes the
+   range will advance the pointer past <thread_number2>.  */
+extern void tid_range_parser_get_tid (struct tid_range_parser *parser,
+				      int *inf_num, int *thr_num);
+
+/* Returns non-zero if parsing has completed.  */
+extern int tid_range_parser_finished (struct tid_range_parser *parser);
+
+/* Return the string being parsed.  When parsing has finished, this
+   points past the last parsed token.  */
+const char *tid_range_parser_string (struct tid_range_parser *parser);
+
+/* True if the TID last parsed was explicitly inferior-qualified.
+   IOW, whether the spec specified an inferior number explicitly.  */
+extern int tid_range_parser_qualified (struct tid_range_parser *parser);
+
+/* Accept a string-form list of thread IDs such as is accepted by
+   tid_range_parser_get_tid.  Return TRUE if the INF_NUM.THR.NUM
+   thread is in the list.  DEFAULT_INFERIOR is the inferior number to
+   assume if a non-qualified thread ID is found in the list.
+
+   By definition, an empty list includes all threads.  This is to be
+   interpreted as typing a command such as "info threads" with no
+   arguments.  */
+extern int tid_is_in_list (const char *list, int default_inferior,
+			   int inf_num, int thr_num);
+
 /* Search function to lookup a thread by 'pid'.  */
 extern struct thread_info *find_thread_ptid (ptid_t ptid);
 
diff --git a/gdb/testsuite/gdb.multi/per-inferior-tids.c b/gdb/testsuite/gdb.multi/per-inferior-tids.c
index 9e77031..00a8298 100644
--- a/gdb/testsuite/gdb.multi/per-inferior-tids.c
+++ b/gdb/testsuite/gdb.multi/per-inferior-tids.c
@@ -18,23 +18,35 @@ 
 #include <unistd.h>
 #include <pthread.h>
 
+pthread_t child_thread[2];
+
 void *
-thread_function (void *arg)
+thread_function2 (void *arg)
 {
   while (1)
     sleep (1);
 }
 
+void *
+thread_function1 (void *arg)
+{
+  pthread_create (&child_thread[1], NULL, thread_function2, NULL);
+
+  while (1)
+    sleep (1);
+}
+
 int
 main (void)
 {
-  pthread_t child_thread;
   int i;
 
   alarm (300);
 
-  pthread_create (&child_thread, NULL, thread_function, NULL);
-  pthread_join (child_thread, NULL);
+  pthread_create (&child_thread[0], NULL, thread_function1, NULL);
+
+  for (i = 0; i < 2; i++)
+    pthread_join (child_thread[i], NULL);
 
   return 0;
 }
diff --git a/gdb/testsuite/gdb.multi/per-inferior-tids.exp b/gdb/testsuite/gdb.multi/per-inferior-tids.exp
index e892758..d947fbc 100644
--- a/gdb/testsuite/gdb.multi/per-inferior-tids.exp
+++ b/gdb/testsuite/gdb.multi/per-inferior-tids.exp
@@ -17,6 +17,14 @@  load_lib gdb-python.exp
 
 standard_testfile
 
+# Multiple inferiors are needed, therefore both native and extended
+# gdbserver modes are supported.  Only non-extended gdbserver is not
+# supported.
+if [target_info exists use_gdb_stub] {
+    untested ${testfile}.exp
+    return
+}
+
 if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} {pthreads debug}] } {
     return -1
 }
@@ -27,10 +35,24 @@  if { ![runto_main] } then {
     return -1
 }
 
+# Helper procedure.  issue "info threads TID_LIST" and expect EXPECTED
+# (a list of thread ids) to be displayed.
+proc info_threads {tid_list expected {message ""}} {
+    set any "\[^\r\n\]*"
+    set expected [string_to_regexp $expected]
+    set r [join $expected " ${any}\r\n${any} "]
+    set r "${any} $r ${any}"
+    set cmd "info threads $tid_list"
+    if {$message == ""} {
+	set message $cmd
+    }
+    gdb_test $cmd $r $message
+}
+
 # "info threads" while there's only inferior 1 should show
 # single-number thread IDs.
 with_test_prefix "single inferior" {
-    gdb_test "info threads" " 1 .* main .* at .*$srcfile:.*"
+    info_threads "" "1"
 
     gdb_test "thread" "Current thread is 1 .*"
 }
@@ -43,7 +65,7 @@  with_test_prefix "two inferiors" {
 
     # Now that we'd added another inferior, thread IDs now show the
     # inferior number.
-    gdb_test "info threads" " 1\.1 .* main .* at .*$srcfile:.*"
+    info_threads "" "1.1"
 
     gdb_test "thread" "Current thread is 1\.1 .*"
 
@@ -54,36 +76,28 @@  with_test_prefix "two inferiors" {
 
     # Now that we'd added another inferior, thread IDs now show the
     # inferior number.
-    gdb_test "info threads" \
-	[multi_line \
-	     "  1\.1 .* main .* at .*$srcfile:.*" \
-	     "\\* 2\.1 .* main .* at .*$srcfile:.*"] \
+    info_threads "" "1.1 2.1" \
 	"info threads show inferior numbers"
 
     gdb_test "thread" "Current thread is 2\.1 .*" \
 	"switch to thread using extended thread ID"
 
-    gdb_breakpoint "thread_function"
+    gdb_breakpoint "thread_function1"
 
     gdb_continue_to_breakpoint "once"
     gdb_test "inferior 1" "Switching to inferior 1 .*"
     gdb_continue_to_breakpoint "twice"
 
-    gdb_test "info threads" \
-	[multi_line \
-	     "  1\.1 .*" \
-	     "\\* 1\.2 .* thread_function .* at .*$srcfile:.*" \
-	     "  2\.1 .*" \
-	     "  2\.2 .* thread_function .* at .*$srcfile:.*"] \
+    info_threads "" "1.1 1.2 2.1 2.2" \
 	"info threads again"
 
     # Same, but show the global ID.
     gdb_test "info threads -gid" \
 	[multi_line \
 	     "  1\.1 +1 +.*" \
-	     "\\* 1\.2 +4 +.* thread_function .* at .*$srcfile:.*" \
+	     "\\* 1\.2 +4 +.* thread_function1 .* at .*$srcfile:.*" \
 	     "  2\.1 +2 +.*" \
-	     "  2\.2 +3 +.* thread_function .* at .*$srcfile:.*"]
+	     "  2\.2 +3 +.* thread_function1 .* at .*$srcfile:.*"]
 
     # Confirm the convenience variables show the expected numbers.
     gdb_test "p \$_thread == 2" " = 1"
@@ -94,12 +108,85 @@  with_test_prefix "two inferiors" {
     # global ID by mistake.
     gdb_test "thread 4" "Unknown thread 1.4\\."
 
+    # Test thread ID list parsing.  Test qualified and unqualified
+    # IDs; qualified and unqualified ranges; invalid IDs and invalid
+    # ranges.
+
+    # First spawn a couple more threads so ranges includes more than
+    # two threads.
+    with_test_prefix "more threads" {
+	gdb_breakpoint "thread_function2"
+
+	gdb_test "inferior 2" "Switching to inferior 2 .*"
+	gdb_continue_to_breakpoint "once"
+
+	gdb_test "inferior 1" "Switching to inferior 1 .*"
+	gdb_continue_to_breakpoint "twice"
+    }
+
+    info_threads "1 2 3" \
+	"1.1 1.2 1.3"
+
+    # Same, but with qualified thread IDs.
+    info_threads "1.1 1.2 1.3 2.1 2.2" \
+	"1.1 1.2 1.3 2.1 2.2"
+
+    # Test a thread number range.
+    info_threads "1-3" \
+	"1.1 1.2 1.3"
+
+    # Same, but using a qualified range.
+    info_threads "1.1-3" \
+	"1.1 1.2 1.3"
+
+    # A mix of qualified and unqualified thread IDs/ranges.
+    info_threads "1.1 2-3" \
+	"1.1 1.2 1.3"
+
+    info_threads "1 1.2-3" \
+	"1.1 1.2 1.3"
+
+    # Likewise, but mix inferiors too.
+    info_threads "2.1 2-3" \
+	"1.2 1.3 2.1"
+
+    # Multiple ranges with mixed explicit inferiors.
+    info_threads "1.1-2 2.2-3" \
+	"1.1 1.2 2.2 2.3"
+
+    # Now test a set of invalid thread IDs/ranges.
+
+    gdb_test "info threads 1." \
+	"Invalid thread ID: 1."
+
+    gdb_test "info threads 1-3 1." \
+	"Invalid thread ID: 1."
+
+    gdb_test "info threads 1.1.1" \
+	"Invalid thread ID: 1.1.1"
+
+    gdb_test "info threads 2 1.1.1" \
+	"Invalid thread ID: 1.1.1"
+
+    gdb_test "info threads 1.1.1 2" \
+	"Invalid thread ID: 1.1.1 2"
+
+    gdb_test "info threads 1-2.1" \
+	"Invalid thread ID: 1-2.1"
+
+    # Check that we do parse the inferior number.
+    gdb_test "info threads 3.1" \
+	"No threads match '3.1'\."
+
     # If Python is configured, check that InferiorThread.global_num
     # returns the expected number.
     if { ![skip_python_tests] } {
-	gdb_py_test_silent_cmd "python t0 = gdb.selected_thread ()" "test gdb.selected_thread" 1
-	gdb_test "python print ('result = %s' % t0.num)" " = 2" "test InferiorThread.num"
-	gdb_test "python print ('result = %s' % t0.global_num)" " = 4" "test InferiorThread.global_num"
+	gdb_py_test_silent_cmd "python t0 = gdb.selected_thread ()" \
+	    "test gdb.selected_thread" 1
+	gdb_test "python print ('result = %s' % t0.num)" " = 3" \
+	    "test InferiorThread.num"
+	gdb_test "python print ('result = %s' % t0.global_num)" " = 6" \
+	    "test InferiorThread.global_num"
     }
 }
 
@@ -112,10 +199,7 @@  with_test_prefix "back to one inferior" {
 
     # "info threads" while there's only inferior 1 should show
     # single-number thread IDs.
-    gdb_test "info threads" \
-	[multi_line \
-	     "\\* 1 .*" \
-	     "  2 .* thread_function .* at .*$srcfile:.*"]
+    info_threads "" "1 2 3"
 
     gdb_test "thread" "Current thread is 1 .*"
 }
@@ -129,10 +213,7 @@  with_test_prefix "single-inferior but not initial" {
 
     # Now that we'd added another inferior, thread IDs should show the
     # inferior number.
-    gdb_test "info threads" \
-	[multi_line \
-	     "\\* 1\.1 .*" \
-	     "  1\.2 .* thread_function .* at .*$srcfile:.*"] \
+    info_threads "" "1.1 1.2 1.3" \
 	"info threads with multiple inferiors"
 
     gdb_test "thread" "Current thread is 1\.1 .*"
@@ -146,7 +227,7 @@  with_test_prefix "single-inferior but not initial" {
 
     # Even though we have a single inferior, its number is > 1, so
     # thread IDs should show the inferior number.
-    gdb_test "info threads" " 3\.1 .* main .* at .*$srcfile:.*" \
+    info_threads "" "3.1" \
 	"info threads with single inferior"
 
     gdb_test "thread" "Current thread is 3\.1 .*" "thread again"
diff --git a/gdb/testsuite/gdb.threads/pthreads.exp b/gdb/testsuite/gdb.threads/pthreads.exp
index b456641..415e2e6 100644
--- a/gdb/testsuite/gdb.threads/pthreads.exp
+++ b/gdb/testsuite/gdb.threads/pthreads.exp
@@ -244,6 +244,49 @@  proc check_backtraces {} {
 	    ".* in main .* in thread1 .* in thread2.*" \
 	    "apply backtrace command to all three threads"
 
+    # Same, but with qualified thread IDs.
+    gdb_test "thread apply 1.1 1.2 1.3 bt" \
+	    ".* in main .* in thread1 .* in thread2.*"
+
+    # Test a thread number range.
+    gdb_test "thread apply 1-3 bt" \
+	    ".* in main .* in thread1 .* in thread2.*"
+
+    # Same, but using a qualified range.
+    gdb_test "thread apply 1.1-3 bt" \
+	    ".* in main .* in thread1 .* in thread2.*"
+
+    # A mix of qualified and unqualified thread IDs/ranges.
+    gdb_test "thread apply 1.1 2-3 bt" \
+	    ".* in main .* in thread1 .* in thread2.*"
+
+    gdb_test "thread apply 1 1.2-3 bt" \
+	    ".* in main .* in thread1 .* in thread2.*"
+
+    # Now test a set of invalid thread IDs/ranges.
+
+    gdb_test "thread apply 1. bt" \
+	"Invalid thread ID: 1. bt"
+
+    gdb_test "thread apply 1-3 1. bt" \
+	"Invalid thread ID: 1. bt"
+
+    gdb_test "thread apply 1.1.1 bt" \
+	"Invalid thread ID: 1.1.1 bt"
+
+    gdb_test "thread apply 2 1.1.1 bt" \
+	"Invalid thread ID: 1.1.1 bt"
+
+    gdb_test "thread apply 1.1.1 2 bt" \
+	"Invalid thread ID: 1.1.1 2 bt"
+
+    gdb_test "thread apply 1-2.1 bt" \
+	"Invalid thread ID: 1-2.1 bt"
+
+    # Check that we do parse the inferior number.
+    gdb_test "thread apply 2.1 bt" \
+	"Unknown thread 2.1"
+
     # Check that we can do thread specific backtraces
     # This also tests that we can do thread specific breakpoints.
 
diff --git a/gdb/testsuite/gdb.threads/thread-find.exp b/gdb/testsuite/gdb.threads/thread-find.exp
index 1af6bbd..ad66e38 100644
--- a/gdb/testsuite/gdb.threads/thread-find.exp
+++ b/gdb/testsuite/gdb.threads/thread-find.exp
@@ -276,9 +276,9 @@  gdb_test_multiple "info threads 3-3" "info threads 3-3" {
 # Test bad input
 
 gdb_test "info thread foo" \
-    "Args must be numbers or '.' variables." \
+    "Invalid thread ID: foo" \
     "info thread foo"
 
 gdb_test "info thread foo -1" \
-    "Args must be numbers or '.' variables." \
+    "Invalid thread ID: foo -1" \
     "info thread foo -1"
diff --git a/gdb/thread.c b/gdb/thread.c
index 8fc7b25..21238c8 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -1126,8 +1126,8 @@  pc_in_thread_step_range (CORE_ADDR pc, struct thread_info *thread)
 }
 
 static int
-should_print_thread (const char *requested_threads, int global_ids,
-		     int pid, struct thread_info *thr)
+should_print_thread (const char *requested_threads, int default_inferior,
+		     int global_ids, int pid, struct thread_info *thr)
 {
   if (pid != -1 && ptid_get_pid (thr->ptid) != pid
       && requested_threads != NULL && *requested_threads != '\0')
@@ -1135,9 +1135,14 @@  should_print_thread (const char *requested_threads, int global_ids,
 
   if (requested_threads != NULL && *requested_threads != '\0')
     {
-      int id = global_ids ? thr->global_num : thr->per_inf_num;
+      int in_list;
 
-      if (number_is_in_list (requested_threads, id))
+      if (global_ids)
+	in_list = number_is_in_list (requested_threads, thr->global_num);
+      else
+	in_list = tid_is_in_list (requested_threads, default_inferior,
+				  thr->inf->num, thr->per_inf_num);
+      if (in_list)
 	{
 	  if (pid != -1 && ptid_get_pid (thr->ptid) != pid)
 	    error (_("Requested thread not found in requested process"));
@@ -1170,6 +1175,7 @@  print_thread_info_1 (struct ui_out *uiout, char *requested_threads,
   const char *extra_info, *name, *target_id;
   int current_thread = -1;
   struct inferior *inf;
+  int current_inf_num = current_inferior ()->num;
 
   update_thread_list ();
   current_ptid = inferior_ptid;
@@ -1188,7 +1194,8 @@  print_thread_info_1 (struct ui_out *uiout, char *requested_threads,
 
       for (tp = thread_list; tp; tp = tp->next)
 	{
-	  if (!should_print_thread (requested_threads, global_ids, pid, tp))
+	  if (!should_print_thread (requested_threads, current_inf_num,
+				    global_ids, pid, tp))
 	    continue;
 
 	  ++n_threads;
@@ -1235,7 +1242,8 @@  print_thread_info_1 (struct ui_out *uiout, char *requested_threads,
       if (ptid_equal (tp->ptid, current_ptid))
 	current_thread = tp->global_num;
 
-      if (!should_print_thread (requested_threads, global_ids, pid, tp))
+      if (!should_print_thread (requested_threads, current_inf_num,
+				global_ids, pid, tp))
 	continue;
 
       chain2 = make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
@@ -1774,13 +1782,16 @@  print_thread_id (struct thread_info *thr)
   return s;
 }
 
+
+/* Implementation of the "thread apply" command.  */
+
 static void
 thread_apply_command (char *tidlist, int from_tty)
 {
   char *cmd;
   struct cleanup *old_chain;
   char *saved_cmd;
-  struct get_number_or_range_state state;
+  struct tid_range_parser parser;
 
   if (tidlist == NULL || *tidlist == '\000')
     error (_("Please specify a thread ID list"));
@@ -1795,33 +1806,44 @@  thread_apply_command (char *tidlist, int from_tty)
   saved_cmd = xstrdup (cmd);
   old_chain = make_cleanup (xfree, saved_cmd);
 
-  init_number_or_range (&state, tidlist);
-  while (!state.finished && state.string < cmd)
-    {
-      struct thread_info *tp;
-      int start;
-
-      start = get_number_or_range (&state);
+  make_cleanup_restore_current_thread ();
 
-      make_cleanup_restore_current_thread ();
+  tid_range_parser_init (&parser, tidlist, current_inferior ()->num);
+  while (!tid_range_parser_finished (&parser)
+	 && tid_range_parser_string (&parser) < cmd)
+    {
+      struct thread_info *tp = NULL;
+      struct inferior *inf;
+      int inf_num, thr_num;
 
-      tp = find_thread_id (current_inferior (), start);
+      tid_range_parser_get_tid (&parser, &inf_num, &thr_num);
+      inf = find_inferior_id (inf_num);
+      if (inf != NULL)
+	tp = find_thread_id (inf, thr_num);
+      if (tp == NULL)
+	{
+	  if (inferior_list->next != NULL || inferior_list->num != 1
+	      || tid_range_parser_qualified (&parser))
+	    warning (_("Unknown thread %d.%d"), inf_num, thr_num);
+	  else
+	    warning (_("Unknown thread %d"), thr_num);
+	  continue;
+	}
 
-      if (!tp)
-	warning (_("Unknown thread %d."), start);
-      else if (!thread_alive (tp))
-	warning (_("Thread %d has terminated."), start);
-      else
+      if (!thread_alive (tp))
 	{
-	  switch_to_thread (tp->ptid);
+	  warning (_("Thread %s has terminated."), print_thread_id (tp));
+	  continue;
+	}
 
-	  printf_filtered (_("\nThread %s (%s):\n"), print_thread_id (tp),
-			   target_pid_to_str (inferior_ptid));
-	  execute_command (cmd, from_tty);
+      switch_to_thread (tp->ptid);
 
-	  /* Restore exact command used previously.  */
-	  strcpy (cmd, saved_cmd);
-	}
+      printf_filtered (_("\nThread %s (%s):\n"), print_thread_id (tp),
+		       target_pid_to_str (inferior_ptid));
+      execute_command (cmd, from_tty);
+
+      /* Restore exact command used previously.  */
+      strcpy (cmd, saved_cmd);
     }
 
   do_cleanups (old_chain);
@@ -2003,6 +2025,143 @@  parse_thread_id (const char *tidstr, const char **end)
   return tp;
 }
 
+/* See gdbthread.h.  */
+
+void
+tid_range_parser_init (struct tid_range_parser *parser, const char *tidlist,
+		       int default_inferior)
+{
+  parser->state = TID_RANGE_STATE_INFERIOR;
+  parser->string = tidlist;
+  parser->inf_num = 0;
+  parser->qualified = 0;
+  parser->default_inferior = default_inferior;
+}
+
+/* See gdbthread.h.  */
+
+int
+tid_range_parser_finished (struct tid_range_parser *parser)
+{
+  switch (parser->state)
+    {
+    case TID_RANGE_STATE_INFERIOR:
+      return *parser->string == '\0';
+    case TID_RANGE_STATE_THREAD_RANGE:
+      return parser->range_parser.finished;
+    }
+
+  gdb_assert_not_reached (_("unhandled state"));
+}
+
+/* See gdbthread.h.  */
+
+const char *
+tid_range_parser_string (struct tid_range_parser *parser)
+{
+  switch (parser->state)
+    {
+    case TID_RANGE_STATE_INFERIOR:
+      return parser->string;
+    case TID_RANGE_STATE_THREAD_RANGE:
+      return parser->range_parser.string;
+    }
+
+  gdb_assert_not_reached (_("unhandled state"));
+}
+
+/* See gdbthread.h.  */
+
+int
+tid_range_parser_qualified (struct tid_range_parser *parser)
+{
+  return parser->qualified;
+}
+
+/* See gdbthread.h.  */
+
+void
+tid_range_parser_get_tid (struct tid_range_parser *parser, int *inf_num,
+			  int *thr_num)
+{
+  if (parser->state == TID_RANGE_STATE_INFERIOR)
+    {
+      const char *p;
+      const char *space;
+
+      space = skip_to_space (parser->string);
+
+      p = parser->string;
+      while (p < space && *p != '.')
+	p++;
+      if (p < space)
+	{
+	  const char *dot = p;
+
+	  /* Parse number to the left of the dot.  */
+	  p = parser->string;
+	  parser->inf_num = get_number_trailer (&p, '.');
+	  if (parser->inf_num == 0)
+	    error (_("Invalid thread ID: %s"), parser->string);
+
+	  parser->qualified = 1;
+	  p = dot + 1;
+
+	  if (isspace (*p))
+	    error (_("Invalid thread ID: %s"), parser->string);
+	}
+      else
+	{
+	  parser->inf_num = parser->default_inferior;
+	  parser->qualified = 0;
+	  p = parser->string;
+	}
+
+      init_number_or_range (&parser->range_parser, p);
+      parser->state = TID_RANGE_STATE_THREAD_RANGE;
+    }
+
+  *inf_num = parser->inf_num;
+  *thr_num = get_number_or_range (&parser->range_parser);
+  if (*thr_num == 0)
+    error (_("Invalid thread ID: %s"), parser->string);
+
+  /* If we successfully parsed a thread number or finished parsing a
+     thread range, switch back to assuming the next TID is
+     inferior-qualified.  */
+  if (parser->range_parser.end_ptr == NULL
+      || parser->range_parser.string == parser->range_parser.end_ptr)
+    {
+      parser->state = TID_RANGE_STATE_INFERIOR;
+      parser->string = parser->range_parser.string;
+    }
+}
+
+/* See gdbthread.h.  */
+
+int
+tid_is_in_list (const char *list, int default_inferior,
+		int inf_num, int thr_num)
+{
+  struct tid_range_parser parser;
+
+  if (list == NULL || *list == '\0')
+    return 1;
+
+  tid_range_parser_init (&parser, list, default_inferior);
+  while (!tid_range_parser_finished (&parser))
+    {
+      int tmp_inf, tmp_thr;
+
+      tid_range_parser_get_tid (&parser, &tmp_inf, &tmp_thr);
+      if (tmp_inf == 0 || tmp_thr == 0)
+	error (_("Invalid thread ID: %s"), parser.string);
+      if (tmp_inf == inf_num && tmp_thr == thr_num)
+	return 1;
+    }
+  return 0;
+}
+
 static int
 do_captured_thread_select (struct ui_out *uiout, void *tidstr_v)
 {