diff mbox

[v3,4/7] Per-inferior/Inferior-qualified thread IDs

Message ID 1452085418-18300-5-git-send-email-palves@redhat.com
State New
Headers show

Commit Message

Pedro Alves Jan. 6, 2016, 1:03 p.m. UTC
This commit changes GDB to track thread numbers per-inferior.  Then,
if you're debugging multiple inferiors, GDB displays
"inferior-num.thread-num" instead of just "thread-num" whenever it
needs to display a thread:

 (gdb) info inferiors
   Num  Description       Executable
   1    process 6022     /home/pedro/gdb/tests/threads
 * 2    process 6037     /home/pedro/gdb/tests/threads
 (gdb) info threads
   Id   Target Id         Frame
   1.1  Thread 0x7ffff7fc2740 (LWP 6022) "threads" (running)
   1.2  Thread 0x7ffff77c0700 (LWP 6028) "threads" (running)
   1.3  Thread 0x7ffff7fc2740 (LWP 6032) "threads" (running)
   2.1  Thread 0x7ffff7fc1700 (LWP 6037) "threads" (running)
   2.2  Thread 0x7ffff77c0700 (LWP 6038) "threads" (running)
 * 2.3  Thread 0x7ffff7fc2740 (LWP 6039) "threads" (running)
 (gdb)
...
 (gdb) thread 1.1
 [Switching to thread 1.1 (Thread 0x7ffff7fc2740 (LWP 8155))]
 (gdb)
...

etc.

You can still use "thread NUM", in which case GDB infers you're
referring to thread NUM of the current inferior.

The $_thread convenience var and Python's InferiorThread.num attribute
are remapped to the new per-inferior thread number.  It's a backward
compatibility break, but since it only matters when debugging multiple
inferiors, I think it's worth doing.

Because MI thread IDs need to be a single integer, we keep giving
threads a global identifier, _in addition_ to the per-inferior number,
and make MI always refer to the global thread IDs.  IOW, nothing
changes from a MI frontend's perspective.

Similarly, since Python's Breakpoint.thread and Guile's
breakpoint-thread/set-breakpoint-thread breakpoint methods need to
work with integers, those are adjusted to work with global thread IDs
too.  Follow up patches will provide convenient means to access
threads' global IDs.

To avoid potencially confusing users (which also avoids updating much
of the testsuite), if there's only one inferior and its ID is "1",
IOW, the user hasn't done anything multi-process/inferior related,
then the "INF." part of thread IDs is not shown.  E.g,.:

 (gdb) info inferiors
   Num  Description       Executable
 * 1    process 15275     /home/pedro/gdb/tests/threads
 (gdb) info threads
   Id   Target Id         Frame
 * 1    Thread 0x7ffff7fc1740 (LWP 15275) "threads" main () at threads.c:40
 (gdb) add-inferior
 Added inferior 2
 (gdb) info threads
   Id   Target Id         Frame
 * 1.1  Thread 0x7ffff7fc1740 (LWP 15275) "threads" main () at threads.c:40
 (gdb)

No regressions on x86_64 Fedora 20.

gdb/ChangeLog:
2016-01-06  Pedro Alves  <palves@redhat.com>

	* NEWS: Mention that thread IDs are now per inferior and global
	thread IDs.
	* Makefile.in (SFILES): Add tid-parse.c.
	(COMMON_OBS): Add tid-parse.o.
	(HFILES_NO_SRCDIR): Add tid-parse.h.
	* ada-tasks.c: Adjust to use ptid_to_global_thread_id.
	* breakpoint.c (insert_breakpoint_locations)
	(remove_threaded_breakpoints, bpstat_check_breakpoint_conditions)
	(print_one_breakpoint_location, set_longjmp_breakpoint)
	(check_longjmp_breakpoint_for_call_dummy)
	(set_momentary_breakpoint): Adjust to use global IDs.
	(find_condition_and_thread, watch_command_1): Use parse_thread_id.
	(until_break_command, longjmp_bkpt_dtor)
	(breakpoint_re_set_thread, insert_single_step_breakpoint): Adjust
	to use global IDs.
	* dummy-frame.c (pop_dummy_frame_bpt): Adjust to use
	ptid_to_global_thread_id.
	* elfread.c (elf_gnu_ifunc_resolver_stop): Likewise.
	* gdbthread.h (struct thread_info): Rename field 'num' to
	'global_num.  Add new fields 'per_inf_num' and 'inf'.
	(thread_id_to_pid): Rename thread_id_to_pid to
	global_thread_id_to_ptid.
	(pid_to_thread_id): Rename to ...
	(ptid_to_global_thread_id): ... this.
	(valid_thread_id): Rename to ...
	(valid_global_thread_id): ... this.
	(find_thread_id): Rename to ...
	(find_thread_global_id): ... this.
	(ALL_THREADS, ALL_THREADS_BY_INFERIOR): Declare.
	(print_thread_info): Add comment.
	* tid-parse.h: New file.
	* tid-parse.c: New file.
	* infcmd.c (step_command_fsm_prepare)
	(step_command_fsm_should_stop): Adjust to use the global thread
	ID.
	(until_next_command, until_next_command)
	(finish_command_fsm_should_stop): Adjust to use the global thread
	ID.
	(attach_post_wait): Adjust to check the inferior number too.
	* inferior.h (struct inferior) <highest_thread_num>: New field.
	* infrun.c (handle_signal_stop)
	(insert_exception_resume_breakpoint)
	(insert_exception_resume_from_probe): Adjust to use the global
	thread ID.
	* record-btrace.c (record_btrace_open): Use global thread IDs.
	* remote.c (process_initial_stop_replies): Also consider the
	inferior number.
	* target.c (target_pre_inferior): Clear the inferior's highest
	thread num.
	* thread.c (clear_thread_inferior_resources): Adjust to use the
	global thread ID.
	(new_thread): New inferior parameter.  Adjust to use it.  Set both
	the thread's global ID and the thread's per-inferior ID.
	(add_thread_silent): Adjust.
	(find_thread_global_id): New.
	(find_thread_id): Make static.  Adjust to rename.
	(valid_thread_id): Rename to ...
	(valid_global_thread_id): ... this.
	(pid_to_thread_id): Rename to ...
	(ptid_to_global_thread_id): ... this.
	(thread_id_to_pid): Rename to ...
	(global_thread_id_to_ptid): ... this.  Adjust.
	(first_thread_of_process): Adjust.
	(do_captured_list_thread_ids): Adjust to use global thread IDs.
	(should_print_thread): New function.
	(print_thread_info): Rename to ...
	(print_thread_info_1): ... this, and add new show_global_ids
	parameter.  Handle it.  Iterate over inferiors.
	(print_thread_info): Reimplement as wrapper around
	print_thread_info_1.
	(show_inferior_qualified_tids): New function.
	(print_thread_id): Use it.
	(tp_array_compar): Compare inferior numbers too.
	(thread_apply_command): Use tid_range_parser.
	(do_captured_thread_select): Use parse_thread_id.
	(thread_id_make_value): Adjust.
	(_initialize_thread): Adjust "info threads" help string.
	* varobj.c (struct varobj_root): Update comment.
	(varobj_create): Adjust to use global thread IDs.
	(value_of_root_1): Adjust to use global_thread_id_to_ptid.
	* cli/cli-utils.c (get_number_trailer): Make extern.
	* cli/cli-utils.h (get_number_trailer): Declare.
	(get_number_const): Adjust documentation.
	* mi/mi-cmd-var.c (mi_cmd_var_update_iter): Adjust to use global
	thread IDs.
	* mi/mi-interp.c (mi_new_thread, mi_thread_exit)
	(mi_on_normal_stop, mi_output_running_pid, mi_on_resume):
	* mi/mi-main.c (mi_execute_command, mi_cmd_execute): Likewise.
	* guile/scm-breakpoint.c (gdbscm_set_breakpoint_thread_x):
	Likewise.
	* python/py-breakpoint.c (bppy_set_thread): Likewise.
	* python/py-finishbreakpoint.c (bpfinishpy_init): Likewise.
	* python/py-infthread.c (thpy_get_num): Add comment and return the
	per-inferior thread ID.
	(thread_object_getset): Update comment of "num".

gdb/testsuite/ChangeLog:
2016-01-06  Pedro Alves  <palves@redhat.com>

	* gdb.base/break.exp: Adjust to output changes.
	* gdb.base/hbreak2.exp: Likewise.
	* gdb.base/sepdebug.exp: Likewise.
	* gdb.base/watch_thread_num.exp: Likewise.
	* gdb.linespec/keywords.exp: Likewise.
	* gdb.multi/info-threads.exp: Likewise.
	* gdb.threads/thread-find.exp: Likewise.
	* gdb.multi/tids.c: New file.
	* gdb.multi/tids.exp: New file.

gdb/doc/ChangeLog:
2016-01-06  Pedro Alves  <palves@redhat.com>

	* gdb.texinfo (Threads): Document per-inferior thread IDs,
	qualified thread IDs, global thread IDs and thread ID lists.
	(Set Watchpoints, Thread-Specific Breakpoints): Adjust to refer to
	thread IDs.
	(Convenience Vars): Document the $_thread convenience variable.
	(Ada Tasks): Adjust to refer to thread IDs.
	(GDB/MI Async Records, GDB/MI Thread Commands, GDB/MI Ada Tasking
	Commands, GDB/MI Variable Objects): Update to mention global
	thread IDs.
	* guile.texi (Breakpoints In Guile)
	<breakpoint-thread/set-breakpoint-thread breakpoint>: Mention
	global thread IDs instead of thread IDs.
	* python.texi (Threads In Python): Adjust documentation of
	InferiorThread.num.
	(Breakpoint.thread): Mention global thread IDs instead of thread
	IDs.
---
 gdb/Makefile.in                             |   7 +-
 gdb/NEWS                                    |  34 +++
 gdb/ada-tasks.c                             |   2 +-
 gdb/breakpoint.c                            |  67 +++---
 gdb/cli/cli-utils.c                         |  11 +-
 gdb/cli/cli-utils.h                         |  15 +-
 gdb/doc/gdb.texinfo                         | 220 +++++++++++-------
 gdb/doc/guile.texi                          |  11 +-
 gdb/doc/python.texi                         |   8 +-
 gdb/dummy-frame.c                           |   2 +-
 gdb/elfread.c                               |   2 +-
 gdb/gdbthread.h                             |  95 ++++++--
 gdb/guile/scm-breakpoint.c                  |   8 +-
 gdb/infcmd.c                                |  16 +-
 gdb/inferior.h                              |   3 +
 gdb/infrun.c                                |   6 +-
 gdb/mi/mi-cmd-var.c                         |   2 +-
 gdb/mi/mi-interp.c                          |  16 +-
 gdb/mi/mi-main.c                            |   6 +-
 gdb/python/py-breakpoint.c                  |   2 +-
 gdb/python/py-finishbreakpoint.c            |   2 +-
 gdb/python/py-infthread.c                   |   7 +-
 gdb/record-btrace.c                         |   2 +-
 gdb/remote.c                                |   4 +-
 gdb/target.c                                |   2 +
 gdb/testsuite/gdb.base/break.exp            |   2 +-
 gdb/testsuite/gdb.base/hbreak2.exp          |   2 +-
 gdb/testsuite/gdb.base/sepdebug.exp         |   2 +-
 gdb/testsuite/gdb.base/watch_thread_num.exp |   2 +-
 gdb/testsuite/gdb.linespec/keywords.exp     |   8 +-
 gdb/testsuite/gdb.multi/info-threads.exp    |   2 +-
 gdb/testsuite/gdb.multi/tids.c              |  52 +++++
 gdb/testsuite/gdb.multi/tids.exp            | 296 ++++++++++++++++++++++++
 gdb/testsuite/gdb.threads/thread-find.exp   |   4 +-
 gdb/thread.c                                | 340 ++++++++++++++++++----------
 gdb/tid-parse.c                             | 267 ++++++++++++++++++++++
 gdb/tid-parse.h                             | 148 ++++++++++++
 gdb/varobj.c                                |   9 +-
 38 files changed, 1364 insertions(+), 320 deletions(-)
 create mode 100644 gdb/testsuite/gdb.multi/tids.c
 create mode 100644 gdb/testsuite/gdb.multi/tids.exp
 create mode 100644 gdb/tid-parse.c
 create mode 100644 gdb/tid-parse.h

Comments

Simon Marchi Jan. 6, 2016, 6:51 p.m. UTC | #1
A few comments:

On 16-01-06 08:03 AM, Pedro Alves wrote:
> This commit changes GDB to track thread numbers per-inferior.  Then,
> if you're debugging multiple inferiors, GDB displays
> "inferior-num.thread-num" instead of just "thread-num" whenever it
> needs to display a thread:
> 
>  (gdb) info inferiors
>    Num  Description       Executable
>    1    process 6022     /home/pedro/gdb/tests/threads
>  * 2    process 6037     /home/pedro/gdb/tests/threads
>  (gdb) info threads
>    Id   Target Id         Frame
>    1.1  Thread 0x7ffff7fc2740 (LWP 6022) "threads" (running)
>    1.2  Thread 0x7ffff77c0700 (LWP 6028) "threads" (running)
>    1.3  Thread 0x7ffff7fc2740 (LWP 6032) "threads" (running)
>    2.1  Thread 0x7ffff7fc1700 (LWP 6037) "threads" (running)
>    2.2  Thread 0x7ffff77c0700 (LWP 6038) "threads" (running)
>  * 2.3  Thread 0x7ffff7fc2740 (LWP 6039) "threads" (running)
>  (gdb)
> ...
>  (gdb) thread 1.1
>  [Switching to thread 1.1 (Thread 0x7ffff7fc2740 (LWP 8155))]
>  (gdb)
> ...

It was already the case before your patch, but I just noticed that
when switching to a running thread, there should probably be a
space before (running):

(gdb) thread 1.1
[Switching to thread 1.1 (Thread 0x7ffff7fad9c0 (LWP 15027))](running)

> diff --git a/gdb/testsuite/gdb.multi/tids.c b/gdb/testsuite/gdb.multi/tids.c
> new file mode 100644
> index 0000000..00a8298
> --- /dev/null
> +++ b/gdb/testsuite/gdb.multi/tids.c
> @@ -0,0 +1,52 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2015 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 <unistd.h>
> +#include <pthread.h>
> +
> +pthread_t child_thread[2];
> +
> +void *
> +thread_function2 (void *arg)
> +{
> +  while (1)
> +    sleep (1);

Nit: this should probably return NULL (thread_function1 as well).

> diff --git a/gdb/testsuite/gdb.multi/tids.exp b/gdb/testsuite/gdb.multi/tids.exp
> new file mode 100644
> index 0000000..7b51c80
> --- /dev/null
> +++ b/gdb/testsuite/gdb.multi/tids.exp
> @@ -0,0 +1,296 @@
> +# Copyright 2015 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 thread ID parsing and display.
> +
> +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
> +}
> +
> +clean_restart ${testfile}
> +
> +if { ![runto_main] } then {
> +    return -1
> +}
> +
> +# Issue "thread apply TID_LIST p 1" and expect EXP_TID_LIST (a list of

Nit: in the code it's "p 1234".

> +# thread ids) to be displayed.
> +proc thread_apply {tid_list exp_tid_list {message ""}} {
> +    global decimal
> +    set any "\[^\r\n\]*"
> +    set expected [string_to_regexp $exp_tid_list]
> +
> +    set r ""
> +    foreach tid $expected {
> +	append r "\[\r\n\]+"
> +	append r "Thread $tid $any:\r\n"
> +	append r "\\$$decimal = 1234"
> +    }
> +
> +    set cmd "thread apply $tid_list"
> +    if {$message == ""} {
> +	set message $cmd
> +    }
> +    gdb_test "$cmd p 1234" $r $message
> +}
> +
> +# Issue "info threads TID_LIST" and expect EXP_TID_LIST (a list of
> +# thread ids) to be displayed.
> +proc info_threads {tid_list exp_tid_list {message ""}} {
> +    set any "\[^\r\n\]*"
> +    set expected [string_to_regexp $exp_tid_list]
> +    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
> +}
> +
> +# Issue both "info threads TID_LIST" and expect INFO_THR output.  Then

"Issue both" does not make sense in this sentence.

> +# issue "thread apply TID_LIST" and expect THR_APPLY output.  If
> +# THR_APPLY is omitted, INFO_THR is expected instead.
> +proc thr_apply_info_thr {tid_list info_thr {thr_apply ""}} {
> +    if {$thr_apply == ""} {
> +	set thr_apply $info_thr
> +    }
> +
> +    info_threads $tid_list $info_thr
> +    thread_apply $tid_list $thr_apply
> +}
> +
> +# Issue both "info threads TID_LIST" and "thread apply TID_LIST" and
> +# expect both commands to error out with EXP_ERROR.
> +proc thr_apply_info_thr_error {tid_list exp_error}  {
> +    gdb_test "info threads $tid_list" \
> +	$exp_error
> +
> +    gdb_test "thread apply $tid_list p 1234" \
> +	$exp_error \
> +	"thread apply $tid_list"
> +}
> +
> +# Issue both "info threads TID_LIST" and "thread apply TID_LIST" and
> +# expect the command to error out with "Invalid thread ID: $EXPECTED".
> +# EXPECTED is a literal string, not a regexp.
> +proc thr_apply_info_thr_invalid {tid_list expected} {
> +    set expected [string_to_regexp $expected]
> +    gdb_test "info threads $tid_list" \
> +	"Invalid thread ID: $expected"
> +
> +    gdb_test "thread apply $tid_list p 1234" \
> +	"Invalid thread ID: $expected p 1234" \
> +	"thread apply $tid_list"
> +}
> +
> +# "info threads" while there's only inferior 1 should show
> +# single-number thread IDs.
> +with_test_prefix "single inferior" {
> +    info_threads "" "1"
> +
> +    gdb_test "thread" "Current thread is 1 .*"
> +}
> +
> +# "info threads" while there are multiple inferiors should show
> +# qualified thread IDs.
> +with_test_prefix "two inferiors" {
> +    # Add another inferior.
> +    gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
> +
> +    # Now that we'd added another inferior, thread IDs now show the

"Now that we've added" ?

> +    # inferior number.
> +    info_threads "" "1.1"
> +
> +    gdb_test "thread" "Current thread is 1\.1 .*"
> +
> +    gdb_test "inferior 2" "Switching to inferior 2 .*" "switch to inferior 2"
> +    gdb_test "file ${binfile}" ".*" "load file in inferior 2"
> +
> +    runto_main
> +
> +    # Now that we'd added another inferior, thread IDs now show the

Again.

> diff --git a/gdb/tid-parse.h b/gdb/tid-parse.h
> new file mode 100644
> index 0000000..3c79c08
> --- /dev/null
> +++ b/gdb/tid-parse.h
> @@ -0,0 +1,148 @@
> +/* TID parsing for GDB, the GNU debugger.
> +   Copyright (C) 2015-2016 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   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/>.  */
> +
> +#ifndef TID_PARSE_H
> +#define TID_PARSE_H
> +
> +#include "cli/cli-utils.h"
> +
> +struct thread_info;
> +
> +/* Issue an invalid thread ID error, pointing at STRING, the invalid
> +   ID.  */
> +extern void ATTRIBUTE_NORETURN invalid_thread_id_error (const char *string);
> +
> +/* Parse TIDSTR as a per-inferior thread ID, in either INF_NUM.THR_NUM
> +   or THR_NUM form.  In the latter case, the missing INF_NUM is filled
> +   in from the current inferior.  If ENDPTR is not NULL,
> +   parse_thread_id stores the address of the first character after the
> +   thread ID.  Either a valid thread is returned, or an error is
> +   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

- <inferior_num>.<thread_number1>-<thread_number1> and will represent
+ <inferior_num>.<thread_number1>-<thread_number2> 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

Again.

> +   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.

Would it be good to precise that it's unrelated to the threads that
exist in gdb?  It iterates solely on the numbers and doesn't care
if that thread really exists.

Also, it might be good to mention what the stop condition for the iteration
is.  Maybe as a simple usage example?

> +
> +   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);

Is there a reason why _get_tid_range returns success/failure and _get_tid
doesn't return anything?  Can't it fail the same way, since the later is
implemented with the former?

> +
> +/* Like tid_range_parser_get_tid, but return a thread range per call,
> +   rather then a single thread.
> +
> +   If the next element in the list is a single thread ID, then
> +   *THR_START and *THR_END are set to the same value.  E.g.:
> +
> +     list: "1.2 3.4-6"
> +     1st call: *INF_NUM=1; *THR_START=2; *THR_END=2
> +     2nd call: *INF_NUM=3; *THR_START=4; *THR_END=4
> +
> +
> +   Returns true if parsed a thread/range successfully, false
> +   otherwise.  */

IIUC, the value of thr_end defines if you want the to get a single thread id,
or a range.  Should it be mentioned here?

> +extern int tid_range_parser_get_tid_range (struct tid_range_parser *parser,
> +					   int *inf_num,
> +					   int *thr_start, int *thr_end);
> +
> +/* 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);
> +
> +/* When parsing a range, advance past the final token in the range.  */
> +extern void tid_range_parser_skip (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

TRUE -> true

> +   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);
> +
> +#endif /* TID_PARSE_H */
> diff --git a/gdb/varobj.c b/gdb/varobj.c
> index d47bdc2..8116a5b 100644
> --- a/gdb/varobj.c
> +++ b/gdb/varobj.c
> @@ -78,7 +78,7 @@ struct varobj_root
>       not NULL.  */
>    struct frame_id frame;
>  
> -  /* The thread ID that this varobj_root belong to.  This field
> +  /* The global thread ID that this varobj_root belongs to.  This field
>       is only valid if valid_block is not NULL.
>       When not 0, indicates which thread 'frame' belongs to.
>       When 0, indicates that the thread list was empty when the varobj_root
> @@ -380,7 +380,7 @@ varobj_create (char *objname,
>  	    error (_("Failed to find the specified frame"));
>  
>  	  var->root->frame = get_frame_id (fi);
> -	  var->root->thread_id = pid_to_thread_id (inferior_ptid);
> +	  var->root->thread_id = ptid_to_global_thread_id (inferior_ptid);
>  	  old_id = get_frame_id (get_selected_frame (NULL));
>  	  select_frame (fi);	 
>  	}
> @@ -2363,8 +2363,9 @@ value_of_root_1 (struct varobj **var_handle)
>      }
>    else
>      {
> -      ptid_t ptid = thread_id_to_pid (var->root->thread_id);
> -      if (in_thread_list (ptid))
> +      ptid_t ptid = global_thread_id_to_ptid (var->root->thread_id);
> +
> +      if (!ptid_equal (minus_one_ptid, ptid))
>  	{
>  	  switch_to_thread (ptid);
>  	  within_scope = check_scope (var);
> 

windows-tdep.c fails to build with this patch.  There is still one usage of find_thread_id:

/home/emaisin/src/binutils-gdb/gdb/windows-tdep.c: In function ‘display_tib’:
/home/emaisin/src/binutils-gdb/gdb/windows-tdep.c:378:7: error: implicit declaration of function ‘find_thread_id’ [-Werror=implicit-function-declaration]
       tp = find_thread_id (gdb_id);
       ^
/home/emaisin/src/binutils-gdb/gdb/windows-tdep.c:378:10: error: assignment makes pointer from integer without a cast [-Werror]
       tp = find_thread_id (gdb_id);
          ^
cc1: all warnings being treated as errors
make: *** [windows-tdep.o] Error 1
Simon Marchi Jan. 7, 2016, 10:01 p.m. UTC | #2
Oh, one comment I forgot:

On 16-01-06 08:03 AM, Pedro Alves wrote:
> +   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.

Because this class^Wstruct and associated functions are disconnected from
the global GDB state (which is great), it would be more accurate to say
something like "from the default passed to the constructor^Winit function.
Pedro Alves Jan. 7, 2016, 10:24 p.m. UTC | #3
On 01/07/2016 10:01 PM, Simon Marchi wrote:
> Oh, one comment I forgot:
> 
> On 16-01-06 08:03 AM, Pedro Alves wrote:
>> +   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.
> 
> Because this class^Wstruct and associated functions are disconnected from
> the global GDB state (which is great), it would be more accurate to say
> something like "from the default passed to the constructor^Winit function.

Thanks for noticing that, I'll fix.  The code used to rely on the
global state when I wrote the comment, then forgot to update it.

(While writing the tests I noticed that "info threads 1.1 2.1 3" would try
to print thread 2.3 instead of 1.3 otherwise, because "info threads" switches
context to each thread being printed, which meant that by the
time we got to "3", the current inferior was 2, while the current
inferior before the command was entered was 1.)

Thanks,
Pedro Alves
diff mbox

Patch

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index d1e0b00..895ece6 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -876,7 +876,7 @@  SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c ada-tasks.c \
 	stabsread.c stack.c probe.c stap-probe.c std-regs.c \
 	symfile.c symfile-debug.c symfile-mem.c symmisc.c symtab.c \
 	target.c target-dcache.c target-descriptions.c target-memory.c \
-	thread.c top.c tracepoint.c \
+	tid-parse.c thread.c top.c tracepoint.c \
 	trad-frame.c \
 	tramp-frame.c \
 	typeprint.c \
@@ -987,7 +987,8 @@  common/common-debug.h common/cleanups.h common/gdb_setjmp.h \
 common/common-exceptions.h target/target.h common/symbol.h \
 common/common-regcache.h fbsd-tdep.h nat/linux-personality.h \
 common/fileio.h nat/x86-linux.h nat/x86-linux-dregs.h \
-nat/linux-namespaces.h arch/arm.h common/gdb_sys_time.h arch/aarch64-insn.h
+nat/linux-namespaces.h arch/arm.h common/gdb_sys_time.h arch/aarch64-insn.h \
+tid-parse.h
 
 # Header files that already have srcdir in them, or which are in objdir.
 
@@ -1029,7 +1030,7 @@  COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \
 	linespec.o dictionary.o namespace.o \
 	location.o infcall.o \
 	infcmd.o infrun.o \
-	expprint.o environ.o stack.o thread.o thread-fsm.o \
+	expprint.o environ.o stack.o tid-parse.o thread.o thread-fsm.o \
 	exceptions.o \
 	extension.o \
 	filesystem.o \
diff --git a/gdb/NEWS b/gdb/NEWS
index 57a6c71..03944383 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,37 @@ 
 
 *** Changes since GDB 7.10
 
+* Per-inferior thread numbers
+
+  Thread numbers are now per inferior instead of global.  If you're
+  debugging multiple inferiors, GDB displays thread IDs using a
+  qualified INF_NUM.THR_NUM form.  For example:
+
+     (gdb) info threads
+       Id   Target Id         Frame
+       1.1  Thread 0x7ffff7fc2740 (LWP 8155) (running)
+       1.2  Thread 0x7ffff7fc1700 (LWP 8168) (running)
+     * 2.1  Thread 0x7ffff7fc2740 (LWP 8157) (running)
+       2.2  Thread 0x7ffff7fc1700 (LWP 8190) (running)
+
+  As consequence, thread numbers as visible in the $_thread
+  convenience variable and in Python's InferiorThread.num attribute
+  are no longer unique between inferiors.
+
+  GDB now maintains a second thread ID per thread, referred to as the
+  global thread ID, which is the new equivalent of thread numbers in
+  previous releases.
+
+  For backwards compatibility, MI's thread IDs always refer to global
+  IDs.
+
+* Commands that accept thread IDs now accept the qualified
+  INF_NUM.THR_NUM form as well.  For example:
+
+     (gdb) thread 2.1
+     [Switching to thread 2.1 (Thread 0x7ffff7fc2740 (LWP 8157))] (running)
+     (gdb)
+
 * The new convenience variable $_inferior holds the number of the
   current inferior.
 
@@ -153,6 +184,9 @@  show remote exec-event-feature-packet
 
 * Python Scripting
 
+  ** The "num" attribute of gdb.InferiorThread objects now refers to
+     the thread's per-inferior number.  See "Per-inferior thread
+     numbers" above.
   ** gdb.InferiorThread objects have a new attribute "inferior", which
      is the Inferior object the thread belongs to.
 
diff --git a/gdb/ada-tasks.c b/gdb/ada-tasks.c
index 062c372..c067ae6 100644
--- a/gdb/ada-tasks.c
+++ b/gdb/ada-tasks.c
@@ -1102,7 +1102,7 @@  print_ada_task_info (struct ui_out *uiout,
       /* Print the associated Thread ID.  */
       if (ui_out_is_mi_like_p (uiout))
         {
-	  const int thread_id = pid_to_thread_id (task_info->ptid);
+	  const int thread_id = ptid_to_global_thread_id (task_info->ptid);
 
 	  if (thread_id != 0)
 	    ui_out_field_int (uiout, "thread-id", thread_id);
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index b2a3c47..72da4ef 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -69,6 +69,7 @@ 
 #include "format.h"
 #include "location.h"
 #include "thread-fsm.h"
+#include "tid-parse.h"
 
 /* readline include files */
 #include "readline/readline.h"
@@ -3144,7 +3145,7 @@  insert_breakpoint_locations (void)
 	 the thread no longer exists.  ALL_BP_LOCATIONS bp_location
 	 has BL->OWNER always non-NULL.  */
       if (bl->owner->thread != -1
-	  && !valid_thread_id (bl->owner->thread))
+	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
       switch_to_program_space_and_thread (bl->pspace);
@@ -3244,7 +3245,7 @@  remove_threaded_breakpoints (struct thread_info *tp, int silent)
 
   ALL_BREAKPOINTS_SAFE (b, b_tmp)
     {
-      if (b->thread == tp->num && user_breakpoint_p (b))
+      if (b->thread == tp->global_num && user_breakpoint_p (b))
 	{
 	  b->disposition = disp_del_at_next_stop;
 
@@ -5447,7 +5448,7 @@  bpstat_check_breakpoint_conditions (bpstat bs, ptid_t ptid)
   /* If this is a thread/task-specific breakpoint, don't waste cpu
      evaluating the condition if this isn't the specified
      thread/task.  */
-  if ((b->thread != -1 && b->thread != pid_to_thread_id (ptid))
+  if ((b->thread != -1 && b->thread != ptid_to_global_thread_id (ptid))
       || (b->task != 0 && b->task != ada_get_task_number (ptid)))
 
     {
@@ -6514,11 +6515,16 @@  print_one_breakpoint_location (struct breakpoint *b,
 
   if (!part_of_multiple && b->thread != -1)
     {
-      struct thread_info *thr = find_thread_id (b->thread);
-
       /* FIXME should make an annotation for this.  */
       ui_out_text (uiout, "\tstop only in thread ");
-      ui_out_field_string (uiout, "thread", print_thread_id (thr));
+      if (ui_out_is_mi_like_p (uiout))
+	ui_out_field_int (uiout, "thread", b->thread);
+      else
+	{
+	  struct thread_info *thr = find_thread_global_id (b->thread);
+
+	  ui_out_field_string (uiout, "thread", print_thread_id (thr));
+	}
       ui_out_text (uiout, "\n");
     }
   
@@ -7574,7 +7580,7 @@  void
 set_longjmp_breakpoint (struct thread_info *tp, struct frame_id frame)
 {
   struct breakpoint *b, *b_tmp;
-  int thread = tp->num;
+  int thread = tp->global_num;
 
   /* To avoid having to rescan all objfile symbols at every step,
      we maintain a list of continually-inserted but always disabled
@@ -7643,7 +7649,7 @@  set_longjmp_breakpoint_for_call_dummy (void)
 	new_b = momentary_breakpoint_from_master (b, bp_longjmp_call_dummy,
 						  &momentary_breakpoint_ops,
 						  1);
-	new_b->thread = pid_to_thread_id (inferior_ptid);
+	new_b->thread = ptid_to_global_thread_id (inferior_ptid);
 
 	/* Link NEW_B into the chain of RETVAL breakpoints.  */
 
@@ -7673,7 +7679,7 @@  check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
   struct breakpoint *b, *b_tmp;
 
   ALL_BREAKPOINTS_SAFE (b, b_tmp)
-    if (b->type == bp_longjmp_call_dummy && b->thread == tp->num)
+    if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num)
       {
 	struct breakpoint *dummy_b = b->related_breakpoint;
 
@@ -8893,7 +8899,7 @@  set_momentary_breakpoint (struct gdbarch *gdbarch, struct symtab_and_line sal,
      momentary breakpoints to be active in only a single thread of
      control.  */
   if (in_thread_list (inferior_ptid))
-    b->thread = pid_to_thread_id (inferior_ptid);
+    b->thread = ptid_to_global_thread_id (inferior_ptid);
 
   update_global_location_list_nothrow (UGLL_MAY_INSERT);
 
@@ -9567,14 +9573,6 @@  check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Issue an invalid thread ID error.  */
-
-static void ATTRIBUTE_NORETURN
-invalid_thread_id_error (int id)
-{
-  error (_("Unknown thread %d."), id);
-}
-
 /* Given TOK, a string specification of condition and thread, as
    accepted by the 'break' command, extract the condition
    string and thread number and set *COND_STRING and *THREAD.
@@ -9623,14 +9621,14 @@  find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	}
       else if (toklen >= 1 && strncmp (tok, "thread", toklen) == 0)
 	{
-	  char *tmptok;
+	  const char *tmptok;
+	  struct thread_info *thr;
 
 	  tok = end_tok + 1;
-	  *thread = strtol (tok, &tmptok, 0);
+	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
 	    error (_("Junk after thread keyword."));
-	  if (!valid_thread_id (*thread))
-	    invalid_thread_id_error (*thread);
+	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
@@ -11135,25 +11133,23 @@  watch_command_1 (const char *arg, int accessflag, int from_tty,
 
 	  if (toklen == 6 && startswith (tok, "thread"))
 	    {
+	      struct thread_info *thr;
 	      /* At this point we've found a "thread" token, which means
 		 the user is trying to set a watchpoint that triggers
 		 only in a specific thread.  */
-	      char *endp;
+	      const char *endp;
 
 	      if (thread != -1)
 		error(_("You can specify only one thread."));
 
 	      /* Extract the thread ID from the next token.  */
-	      thread = strtol (value_start, &endp, 0);
+	      thr = parse_thread_id (value_start, &endp);
 
-	      /* Check if the user provided a valid numeric value for the
-		 thread ID.  */
+	      /* Check if the user provided a valid thread ID.  */
 	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		error (_("Invalid thread ID specification %s."), value_start);
+		invalid_thread_id_error (value_start);
 
-	      /* Check if the thread actually exists.  */
-	      if (!valid_thread_id (thread))
-		invalid_thread_id_error (thread);
+	      thread = thr->global_num;
 	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
@@ -11694,7 +11690,7 @@  until_break_command (char *arg, int from_tty, int anywhere)
   resolve_sal_pc (&sal);
 
   tp = inferior_thread ();
-  thread = tp->num;
+  thread = tp->global_num;
 
   old_chain = make_cleanup (null_cleanup, NULL);
 
@@ -11744,7 +11740,8 @@  until_break_command (char *arg, int from_tty, int anywhere)
 						    stack_frame_id, bp_until);
   make_cleanup_delete_breakpoint (location_breakpoint);
 
-  sm = new_until_break_fsm (tp->num, location_breakpoint, caller_breakpoint);
+  sm = new_until_break_fsm (tp->global_num,
+			    location_breakpoint, caller_breakpoint);
   tp->thread_fsm = &sm->thread_fsm;
 
   discard_cleanups (old_chain);
@@ -13366,7 +13363,7 @@  momentary_bkpt_print_mention (struct breakpoint *b)
 static void
 longjmp_bkpt_dtor (struct breakpoint *self)
 {
-  struct thread_info *tp = find_thread_id (self->thread);
+  struct thread_info *tp = find_thread_global_id (self->thread);
 
   if (tp)
     tp->initiating_frame = null_frame_id;
@@ -14547,7 +14544,7 @@  breakpoint_re_set_thread (struct breakpoint *b)
   if (b->thread != -1)
     {
       if (in_thread_list (inferior_ptid))
-	b->thread = pid_to_thread_id (inferior_ptid);
+	b->thread = ptid_to_global_thread_id (inferior_ptid);
 
       /* We're being called after following a fork.  The new fork is
 	 selected as current, and unless this was a vfork will have a
@@ -15058,7 +15055,7 @@  insert_single_step_breakpoint (struct gdbarch *gdbarch,
   if (tp->control.single_step_breakpoints == NULL)
     {
       tp->control.single_step_breakpoints
-	= new_single_step_breakpoint (tp->num, gdbarch);
+	= new_single_step_breakpoint (tp->global_num, gdbarch);
     }
 
   sal = find_pc_line (pc, 0);
diff --git a/gdb/cli/cli-utils.c b/gdb/cli/cli-utils.c
index 08b099f..a68b67d 100644
--- a/gdb/cli/cli-utils.c
+++ b/gdb/cli/cli-utils.c
@@ -23,16 +23,9 @@ 
 
 #include <ctype.h>
 
-/* *PP is a string denoting a number.  Get the number of the.  Advance
-   *PP after the string and any trailing whitespace.
-
-   Currently the string can either be a number, or "$" followed by the
-   name of a convenience variable, or ("$" or "$$") followed by digits.
-
-   TRAILER is a character which can be found after the number; most
-   commonly this is `-'.  If you don't want a trailer, use \0.  */
+/* See documentation in cli-utils.h.  */
 
-static int
+int
 get_number_trailer (const char **pp, int trailer)
 {
   int retval = 0;	/* default */
diff --git a/gdb/cli/cli-utils.h b/gdb/cli/cli-utils.h
index ace46b4..ebf11f2 100644
--- a/gdb/cli/cli-utils.h
+++ b/gdb/cli/cli-utils.h
@@ -20,11 +20,18 @@ 
 #ifndef CLI_UTILS_H
 #define CLI_UTILS_H
 
-/* *PP is a string denoting a number.  Get the number of the.  Advance
-   *PP after the string and any trailing whitespace.
+/* *PP is a string denoting a number.  Get the number.  Advance *PP
+   after the string and any trailing whitespace.
 
-   Currently the string can either be a number,  or "$" followed by the
-   name of a convenience variable, or ("$" or "$$") followed by digits.  */
+   The string can either be a number, or "$" followed by the name of a
+   convenience variable, or ("$" or "$$") followed by digits.
+
+   TRAILER is a character which can be found after the number; most
+   commonly this is `-'.  If you don't want a trailer, use \0.  */
+
+extern int get_number_trailer (const char **pp, int trailer);
+
+/* Convenience.  Like get_number_trailer, but with no TRAILER.  */
 
 extern int get_number_const (const char **);
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index f01b703..1bf2208 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -2838,9 +2838,9 @@  programs:
 
 @itemize @bullet
 @item automatic notification of new threads
-@item @samp{thread @var{threadno}}, a command to switch among threads
+@item @samp{thread @var{thread-id}}, a command to switch among threads
 @item @samp{info threads}, a command to inquire about existing threads
-@item @samp{thread apply [@var{threadno}] [@var{all}] @var{args}},
+@item @samp{thread apply [@var{thread-id}] [@var{all}] @var{args}},
 a command to apply a command to a list of threads
 @item thread-specific breakpoints
 @item @samp{set print thread-events}, which controls printing of 
@@ -2886,26 +2886,77 @@  further qualifier.
 @c         multithread systems permit starting a program with multiple
 @c         threads ab initio?
 
-@cindex thread number
+@anchor{thread numbers}
+@cindex thread number, per inferior
 @cindex thread identifier (GDB)
-For debugging purposes, @value{GDBN} associates its own thread
-number---always a single integer---with each thread in your program.
+For debugging purposes, @value{GDBN} associates its own thread number
+---always a single integer---with each thread of an inferior.  This
+number is unique between all threads of an inferior, but not unique
+between threads of different inferiors.
+
+@cindex qualified thread ID
+You can refer to a given thread in an inferior using the qualified
+@var{inferior-num}.@var{thread-num} syntax, also known as
+@dfn{qualified thread ID}, with @var{inferior-num} being the inferior
+number and @var{thread-num} being the thread number of the given
+inferior.  For example, thread @code{2.3} refers to thread number 3 of
+inferior 2.  If you omit @var{inferior-num} (e.g., @code{thread 3}),
+then @value{GDBN} infers you're referring to a thread of the current
+inferior.
+
+Until you create a second inferior, @value{GDBN} does not show the
+@var{inferior-num} part of thread IDs, even though you can always use
+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)
+In addition to a @emph{per-inferior} number, each thread is also
+assigned a unique @emph{global} number, also known as @dfn{global
+thread ID}, a single integer.  Unlike the thread number component of
+the thread ID, no two threads have the same global ID, even when
+you're debugging multiple inferiors.
 
 From @value{GDBN}'s perspective, a process always has at least one
 thread.  In other words, @value{GDBN} assigns a thread number to the
 program's ``main thread'' even if the program is not multi-threaded.
 
+@vindex $_thread@r{, convenience variable}
+The debugger convenience variable @samp{$_thread} contains the
+per-inferior thread number of the current thread.  You may find this
+useful in writing breakpoint conditional expressions, command scripts,
+and so forth.  @xref{Convenience Vars,, Convenience Variables}, for
+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
 @item
-the thread number assigned by @value{GDBN}
+the per-inferior thread number assigned by @value{GDBN}
 
 @item
 the target system's thread identifier (@var{systag})
@@ -2936,6 +2987,19 @@  For example,
     at threadtest.c:68
 @end smallexample
 
+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:
+
+@smallexample
+(@value{GDBP}) info threads
+  Id   Target Id             Frame
+  1.1  process 35 thread 13  main (argc=1, argv=0x7ffffff8)
+  1.2  process 35 thread 23  0x34e5 in sigpause ()
+  1.3  process 35 thread 27  0x34e5 in sigpause ()
+* 2.1  process 65 thread 1   main (argc=1, argv=0x7ffffff8)
+@end smallexample
+
 On Solaris, you can display more information about user threads with a
 Solaris-specific command:
 
@@ -2947,13 +3011,15 @@  Display info on Solaris user threads.
 @end table
 
 @table @code
-@kindex thread @var{threadno}
-@item thread @var{threadno}
-Make thread number @var{threadno} the current thread.  The command
-argument @var{threadno} is the internal @value{GDBN} thread number, as
-shown in the first field of the @samp{info threads} display.
-@value{GDBN} responds by displaying the system identifier of the thread
-you selected, and its current stack frame summary:
+@kindex thread @var{thread-id}
+@item thread @var{thread-id}
+Make thread ID @var{thread-id} the current thread.  The command
+argument @var{thread-id} is the @value{GDBN} 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}).
+
+@value{GDBN} responds by displaying the system identifier of the
+thread you selected, and its current stack frame summary:
 
 @smallexample
 (@value{GDBP}) thread 2
@@ -2967,23 +3033,14 @@  As with the @samp{[New @dots{}]} message, the form of the text after
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
-@vindex $_thread@r{, convenience variable}
-The debugger convenience variable @samp{$_thread} contains the number
-of the current thread.  You may find this useful in writing breakpoint
-conditional expressions, command scripts, and so forth.  See
-@xref{Convenience Vars,, Convenience Variables}, for general
-information on convenience variables.
-
 @kindex thread apply
 @cindex apply command to several threads
-@item thread apply [@var{threadno} | 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{threadno}.  It can be a single thread number, one of the numbers
-shown in the first field of the @samp{info threads} display; 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 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}}.
 
@@ -3983,7 +4040,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
@@ -3993,9 +4050,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.
@@ -4027,12 +4084,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.
 
@@ -6051,25 +6108,25 @@  breakpoints on all threads, or on a particular thread.
 @table @code
 @cindex breakpoints and threads
 @cindex thread breakpoints
-@kindex break @dots{} thread @var{threadno}
-@item break @var{location} thread @var{threadno}
-@itemx break @var{location} thread @var{threadno} if @dots{}
+@kindex break @dots{} thread @var{thread-id}
+@item break @var{location} thread @var{thread-id}
+@itemx break @var{location} thread @var{thread-id} if @dots{}
 @var{location} specifies source lines; there are several ways of
 writing them (@pxref{Specify Location}), but the effect is always to
 specify some source line.
 
-Use the qualifier @samp{thread @var{threadno}} with a breakpoint command
+Use the qualifier @samp{thread @var{thread-id}} with a breakpoint command
 to specify that you only want @value{GDBN} to stop the program when a
-particular thread reaches this breakpoint.  The @var{threadno} specifier
-is one of the numeric thread identifiers assigned by @value{GDBN}, shown
+particular thread reaches this breakpoint.  The @var{thread-id} specifier
+is one of the thread identifiers assigned by @value{GDBN}, shown
 in the first column of the @samp{info threads} display.
 
-If you do not specify @samp{thread @var{threadno}} when you set a
+If you do not specify @samp{thread @var{thread-id}} when you set a
 breakpoint, the breakpoint applies to @emph{all} threads of your
 program.
 
 You can use the @code{thread} qualifier on conditional breakpoints as
-well; in this case, place @samp{thread @var{threadno}} before or
+well; in this case, place @samp{thread @var{thread-id}} before or
 after the breakpoint condition, like this:
 
 @smallexample
@@ -10392,6 +10449,9 @@  This variable contains the address of the thread information block.
 The number of the current inferior.  @xref{Inferiors and
 Programs, ,Debugging Multiple Inferiors and Programs}.
 
+@item $_thread
+The thread number of the current thread.  @xref{thread numbers}.
+
 @end table
 
 @node Convenience Funs
@@ -16134,7 +16194,7 @@  This command prints the ID of the current task.
 
 @item task @var{taskno}
 @cindex Ada task switching
-This command is like the @code{thread @var{threadno}}
+This command is like the @code{thread @var{thread-id}}
 command (@pxref{Threads}).  It switches the context of debugging
 from the current task to the given task.
 
@@ -25479,8 +25539,8 @@  increases the risk that by relying on implicitly selected thread, the
 frontend may be operating on a wrong one.  Therefore, each MI command
 should explicitly specify which thread and frame to operate on.  To
 make it possible, each MI command accepts the @samp{--thread} and
-@samp{--frame} options, the value to each is @value{GDBN} identifier
-for thread and frame to operate on.
+@samp{--frame} options, the value to each is @value{GDBN} global
+identifier for thread and frame to operate on.
 
 Usually, each top-level window in a frontend allows the user to select
 a thread and a frame, and remembers the user selection for further
@@ -26033,15 +26093,16 @@  The following is the list of possible async records:
 @table @code
 
 @item *running,thread-id="@var{thread}"
-The target is now running.  The @var{thread} field tells which
-specific thread is now running, and can be @samp{all} if all threads
-are running.  The frontend should assume that no interaction with a 
-running thread is possible after this notification is produced.
-The frontend should not assume that this notification is output
-only once for any command.  @value{GDBN} may emit this notification 
-several times, either for different threads, because it cannot resume
-all threads together, or even for a single thread, if the thread must
-be stepped though some code before letting it run freely.
+The target is now running.  The @var{thread} field can be the global
+thread ID of the the thread that is now running, and it can be
+@samp{all} if all threads are running.  The frontend should assume
+that no interaction with a running thread is possible after this
+notification is produced.  The frontend should not assume that this
+notification is output only once for any command.  @value{GDBN} may
+emit this notification several times, either for different threads,
+because it cannot resume all threads together, or even for a single
+thread, if the thread must be stepped though some code before letting
+it run freely.
 
 @item *stopped,reason="@var{reason}",thread-id="@var{id}",stopped-threads="@var{stopped}",core="@var{core}"
 The target has stopped.  The @var{reason} field can have one of the
@@ -26095,8 +26156,9 @@  The inferior called @code{exec}.  This is reported when @code{catch exec}
 (@pxref{Set Catchpoints}) has been used.
 @end table
 
-The @var{id} field identifies the thread that directly caused the stop
--- for example by hitting a breakpoint.  Depending on whether all-stop
+The @var{id} field identifies the global thread ID of the thread
+that directly caused the stop -- for example by hitting a breakpoint.
+Depending on whether all-stop
 mode is in effect (@pxref{All-Stop Mode}), @value{GDBN} may either
 stop all threads, or only the thread that directly triggered the stop.
 If all threads are stopped, the @var{stopped} field will have the
@@ -26132,7 +26194,7 @@  only when the inferior exited with some code.
 @item =thread-created,id="@var{id}",group-id="@var{gid}"
 @itemx =thread-exited,id="@var{id}",group-id="@var{gid}"
 A thread either was created, or has exited.  The @var{id} field
-contains the @value{GDBN} identifier of the thread.  The @var{gid}
+contains the global @value{GDBN} identifier of the thread.  The @var{gid}
 field identifies the thread group this thread belongs to.
 
 @item =thread-selected,id="@var{id}"
@@ -26393,7 +26455,7 @@  uses a tuple with the following fields:
 
 @table @code
 @item id
-The numeric id assigned to the thread by @value{GDBN}.  This field is
+The global numeric id assigned to the thread by @value{GDBN}.  This field is
 always present.
 
 @item target-id
@@ -26866,7 +26928,8 @@  Make the breakpoint conditional on @var{condition}.
 @item -i @var{ignore-count}
 Initialize the @var{ignore-count}.
 @item -p @var{thread-id}
-Restrict the breakpoint to the specified @var{thread-id}.
+Restrict the breakpoint to the thread with the specified global
+@var{thread-id}.
 @end table
 
 @subsubheading Result
@@ -26956,7 +27019,8 @@  Make the breakpoint conditional on @var{condition}.
 Set the ignore count of the breakpoint (@pxref{Conditions, ignore count})
 to @var{ignore-count}.
 @item -p @var{thread-id}
-Restrict the breakpoint to the specified @var{thread-id}.
+Restrict the breakpoint to the thread with the specified global
+@var{thread-id}.
 @end table
 
 @subsubheading Result
@@ -27606,10 +27670,11 @@  The corresponding @value{GDBN} command is @samp{pwd}.
  -thread-info [ @var{thread-id} ]
 @end smallexample
 
-Reports information about either a specific thread, if 
-the @var{thread-id} parameter is present, or about all
-threads.  When printing information about all threads,
-also reports the current thread.
+Reports information about either a specific thread, if the
+@var{thread-id} parameter is present, or about all threads.
+@var{thread-id} is the thread's global thread ID.  When printing
+information about all threads, also reports the global ID of the
+current thread.
 
 @subsubheading @value{GDBN} Command
 
@@ -27626,7 +27691,7 @@  defined for a given thread:
 This field exists only for the current thread.  It has the value @samp{*}.
 
 @item id
-The identifier that @value{GDBN} uses to refer to the thread.
+The global identifier that @value{GDBN} uses to refer to the thread.
 
 @item target-id
 The identifier that the target uses to refer to the thread.
@@ -27692,8 +27757,9 @@  current-thread-id="1"
  -thread-list-ids
 @end smallexample
 
-Produces a list of the currently known @value{GDBN} thread ids.  At the
-end of the list it also prints the total number of such threads.
+Produces a list of the currently known global @value{GDBN} thread ids.
+At the end of the list it also prints the total number of such
+threads.
 
 This command is retained for historical reasons, the
 @code{-thread-info} command should be used instead.
@@ -27719,11 +27785,12 @@  current-thread-id="1",number-of-threads="3"
 @subsubheading Synopsis
 
 @smallexample
- -thread-select @var{threadnum}
+ -thread-select @var{thread-id}
 @end smallexample
 
-Make @var{threadnum} the current thread.  It prints the number of the new
-current thread, and the topmost frame for that thread.
+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.
 
 This command is deprecated in favor of explicitly using the
 @samp{--thread} option to each command.
@@ -27792,7 +27859,8 @@  The identifier that @value{GDBN} uses to refer to the Ada task.
 The identifier that the target uses to refer to the Ada task.
 
 @item thread-id
-The identifier of the thread corresponding to the Ada task.
+The global thread identifier of the thread corresponding to the Ada
+task.
 
 This field should always exist, as Ada tasks are always implemented
 on top of a thread.  But if @value{GDBN} cannot find this corresponding
@@ -29008,7 +29076,7 @@  would be printed by the @value{GDBN} CLI.  If @samp{print object}
 
 @item thread-id
 If a variable object is bound to a specific thread, then this is the
-thread's identifier.
+thread's global identifier.
 
 @item has_more
 For a dynamic varobj, this indicates whether there appear to be any
@@ -29189,8 +29257,8 @@  The type of the child.  If @samp{print object}
 If values were requested, this is the value.
 
 @item thread-id
-If this variable object is associated with a thread, this is the thread id.  
-Otherwise this result is not present.
+If this variable object is associated with a thread, this is the
+thread's global thread id.  Otherwise this result is not present.
 
 @item frozen
 If the variable object is frozen, this variable will be present with a value of 1.
diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
index fa1ad53..840defc 100644
--- a/gdb/doc/guile.texi
+++ b/gdb/doc/guile.texi
@@ -3127,13 +3127,14 @@  At present, @var{count} must be zero.
 @end deffn
 
 @deffn {Scheme Procedure} breakpoint-thread breakpoint
-Return the thread-id for thread-specific breakpoint @var{breakpoint}.
-Return #f if @var{breakpoint} is not thread-specific.
+Return the global-thread-id for thread-specific breakpoint
+@var{breakpoint}.  Return #f if @var{breakpoint} is not
+thread-specific.
 @end deffn
 
-@deffn {Scheme Procedure} set-breakpoint-thread! breakpoint thread-id|#f
-Set the thread-id for @var{breakpoint} to @var{thread-id}.
-If set to @code{#f}, the breakpoint is no longer thread-specific.
+@deffn {Scheme Procedure} set-breakpoint-thread! breakpoint global-thread-id|#f
+Set the thread-id for @var{breakpoint} to @var{global-thread-id} If
+set to @code{#f}, the breakpoint is no longer thread-specific.
 @end deffn
 
 @deffn {Scheme Procedure} breakpoint-task breakpoint
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 855da44..f9f9e5b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -2995,7 +2995,7 @@  user-specified thread name.
 @end defvar
 
 @defvar InferiorThread.num
-ID of the thread, as assigned by GDB.
+The per-inferior number of the thread, as assigned by GDB.
 @end defvar
 
 @defvar InferiorThread.ptid
@@ -4643,9 +4643,9 @@  first command is @code{silent}.  This is not reported by the
 @end defvar
 
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the thread
-id.  If the breakpoint is not thread-specific, this attribute is
-@code{None}.  This attribute is writable.
+If the breakpoint is thread-specific, this attribute holds the
+thread's global id.  If the breakpoint is not thread-specific, this
+attribute is @code{None}.  This attribute is writable.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/dummy-frame.c b/gdb/dummy-frame.c
index 6e9dc2e..606fb85 100644
--- a/gdb/dummy-frame.c
+++ b/gdb/dummy-frame.c
@@ -130,7 +130,7 @@  pop_dummy_frame_bpt (struct breakpoint *b, void *dummy_voidp)
 {
   struct dummy_frame *dummy = (struct dummy_frame *) dummy_voidp;
 
-  if (b->thread == pid_to_thread_id (dummy->id.ptid)
+  if (b->thread == ptid_to_global_thread_id (dummy->id.ptid)
       && b->disposition == disp_del && frame_id_eq (b->frame_id, dummy->id.id))
     {
       while (b->related_breakpoint != b)
diff --git a/gdb/elfread.c b/gdb/elfread.c
index 1eb341b..55674f3 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -902,7 +902,7 @@  elf_gnu_ifunc_resolver_stop (struct breakpoint *b)
   struct frame_info *prev_frame = get_prev_frame (get_current_frame ());
   struct frame_id prev_frame_id = get_stack_frame_id (prev_frame);
   CORE_ADDR prev_pc = get_frame_pc (prev_frame);
-  int thread_id = pid_to_thread_id (inferior_ptid);
+  int thread_id = ptid_to_global_thread_id (inferior_ptid);
 
   gdb_assert (b->type == bp_gnu_ifunc_resolver);
 
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index e1e264a..6c23687 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),...  */
@@ -187,7 +188,45 @@  struct thread_info
   ptid_t ptid;			/* "Actual process id";
 				    In fact, this may be overloaded with 
 				    kernel thread id, etc.  */
-  int num;			/* Convenient handle (GDB thread id) */
+
+  /* Each thread has two GDB IDs.
+
+     a) The thread ID (Id).  This consists of the pair of:
+
+        - the number of the thread's inferior and,
+
+        - the thread's thread number in its inferior, aka, the
+          per-inferior thread number.  This number is unique in the
+          inferior but not unique between inferiors.
+
+     b) The global ID (GId).  This is a a single integer unique
+        between all inferiors.
+
+     E.g.:
+
+      (gdb) info threads -gid
+	Id    GId   Target Id   Frame
+      * 1.1   1     Thread A    0x16a09237 in foo () at foo.c:10
+	1.2   3     Thread B    0x15ebc6ed in bar () at foo.c:20
+	1.3   5     Thread C    0x15ebc6ed in bar () at foo.c:20
+	2.1   2     Thread A    0x16a09237 in foo () at foo.c:10
+	2.2   4     Thread B    0x15ebc6ed in bar () at foo.c:20
+	2.3   6     Thread C    0x15ebc6ed in bar () at foo.c:20
+
+     Above, both inferiors 1 and 2 have threads numbered 1-3, but each
+     thread has its own unique global ID.  */
+
+  /* The thread's global GDB thread number.  This is exposed to MI and
+     Python/Scheme.  */
+  int global_num;
+
+  /* The per-inferior thread number.  This is unique in the inferior
+     the thread belongs to, but not unique between inferiors.  This is
+     what the $_thread convenience variable is bound to.  */
+  int per_inf_num;
+
+  /* The inferior this thread belongs to.  */
+  struct inferior *inf;
 
   /* The name of the thread, as specified by the user.  This is NULL
      if the thread does not have a user-given name.  */
@@ -353,31 +392,40 @@  extern int thread_has_single_step_breakpoint_here (struct thread_info *tp,
 						   struct address_space *aspace,
 						   CORE_ADDR addr);
 
-/* Translate the integer thread id (GDB's homegrown id, not the system's)
-   into a "pid" (which may be overloaded with extra thread information).  */
-extern ptid_t thread_id_to_pid (int);
+/* Translate the global integer thread id (GDB's homegrown id, not the
+   system's) into a "pid" (which may be overloaded with extra thread
+   information).  */
+extern ptid_t global_thread_id_to_ptid (int num);
+
+/* Translate a 'pid' (which may be overloaded with extra thread
+   information) into the global integer thread id (GDB's homegrown id,
+   not the system's).  */
+extern int ptid_to_global_thread_id (ptid_t ptid);
 
-/* Translate a 'pid' (which may be overloaded with extra thread information) 
-   into the integer thread id (GDB's homegrown id, not the system's).  */
-extern int pid_to_thread_id (ptid_t ptid);
+/* Returns whether to show inferior-qualified thread IDs, or plain
+   thread numbers.  Inferior-qualified IDs are shown whenever we have
+   multiple inferiors, or the only inferior left has number > 1.  */
+extern int show_inferior_qualified_tids (void);
 
-/* Return a string version of THR's thread ID.  The result is stored
-   in a circular static buffer, NUMCELLS deep.  */
+/* Return a string version of THR's thread ID.  If there are multiple
+   inferiors, then this prints the inferior-qualifier form, otherwise
+   it only prints the thread number.  The result is stored in a
+   circular static buffer, NUMCELLS deep.  */
 const char *print_thread_id (struct thread_info *thr);
 
 /* Boolean test for an already-known pid (which may be overloaded with
    extra thread information).  */
 extern int in_thread_list (ptid_t ptid);
 
-/* Boolean test for an already-known thread id (GDB's homegrown id, 
-   not the system's).  */
-extern int valid_thread_id (int thread);
+/* Boolean test for an already-known global thread id (GDB's homegrown
+   global id, not the system's).  */
+extern int valid_global_thread_id (int global_id);
 
 /* Search function to lookup a thread by 'pid'.  */
 extern struct thread_info *find_thread_ptid (ptid_t ptid);
 
-/* Find thread by GDB user-visible thread number.  */
-struct thread_info *find_thread_id (int num);
+/* Find thread by GDB global thread ID.  */
+struct thread_info *find_thread_global_id (int global_id);
 
 /* Finds the first thread of the inferior given by PID.  If PID is -1,
    returns the first thread in the list.  */
@@ -399,6 +447,16 @@  void thread_change_ptid (ptid_t old_ptid, ptid_t new_ptid);
 typedef int (*thread_callback_func) (struct thread_info *, void *);
 extern struct thread_info *iterate_over_threads (thread_callback_func, void *);
 
+/* Traverse all threads.  */
+#define ALL_THREADS(T)				\
+  for (T = thread_list; T; T = T->next)		\
+
+/* Traverse over all threads, sorted by inferior.  */
+#define ALL_THREADS_BY_INFERIOR(inf, tp) \
+  ALL_INFERIORS (inf) \
+    ALL_THREADS (tp) \
+      if (inf == tp->inf)
+
 /* Traverse all threads, except those that have THREAD_EXITED
    state.  */
 
@@ -504,7 +562,14 @@  extern void thread_command (char *tidstr, int from_tty);
    `set print thread-events'.  */
 extern int print_thread_events;
 
-extern void print_thread_info (struct ui_out *uiout, char *threads,
+/* Prints the list of threads and their details on UIOUT.  If
+   REQUESTED_THREADS, a list of GDB ids/ranges, is not NULL, only
+   print threads whose ID is included in the list.  If PID is not -1,
+   only print threads from the process PID.  Otherwise, threads from
+   all attached PIDs are printed.  If both REQUESTED_THREADS is not
+   NULL and PID is not -1, then the thread is printed if it belongs to
+   the specified process.  Otherwise, an error is raised.  */
+extern void print_thread_info (struct ui_out *uiout, char *requested_threads,
 			       int pid);
 
 extern struct cleanup *make_cleanup_restore_current_thread (void);
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index 46f9918..716fe4c 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -745,7 +745,7 @@  gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   if (scm_is_signed_integer (newvalue, LONG_MIN, LONG_MAX))
     {
       id = scm_to_long (newvalue);
-      if (! valid_thread_id (id))
+      if (!valid_global_thread_id (id))
 	{
 	  gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, newvalue,
 				     _("invalid thread id"));
@@ -1273,14 +1273,14 @@  Set the breakpoint's \"hit\" count.  The value must be zero.\n\
 
   { "breakpoint-thread", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_thread),
     "\
-Return the breakpoint's thread id or #f if there isn't one." },
+Return the breakpoint's global thread id or #f if there isn't one." },
 
   { "set-breakpoint-thread!", 2, 0, 0,
     as_a_scm_t_subr (gdbscm_set_breakpoint_thread_x),
     "\
-Set the thread id for this breakpoint.\n\
+Set the global thread id for this breakpoint.\n\
 \n\
-  Arguments: <gdb:breakpoint> thread-id" },
+  Arguments: <gdb:breakpoint> global-thread-id" },
 
   { "breakpoint-task", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_task),
     "\
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index d46e8a7..df13896 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -960,7 +960,7 @@  step_command_fsm_prepare (struct step_command_fsm *sm,
   sm->skip_subroutines = skip_subroutines;
   sm->single_inst = single_inst;
   sm->count = count;
-  sm->thread = thread->num;
+  sm->thread = thread->global_num;
 
   /* Leave the si command alone.  */
   if (!sm->single_inst || sm->skip_subroutines)
@@ -1032,7 +1032,7 @@  static int
 step_command_fsm_should_stop (struct thread_fsm *self)
 {
   struct step_command_fsm *sm = (struct step_command_fsm *) self;
-  struct thread_info *tp = find_thread_id (sm->thread);
+  struct thread_info *tp = find_thread_global_id (sm->thread);
 
   if (tp->control.stop_step)
     {
@@ -1478,7 +1478,7 @@  until_next_command (int from_tty)
   struct symbol *func;
   struct symtab_and_line sal;
   struct thread_info *tp = inferior_thread ();
-  int thread = tp->num;
+  int thread = tp->global_num;
   struct cleanup *old_chain;
   struct until_next_fsm *sm;
 
@@ -1520,12 +1520,11 @@  until_next_command (int from_tty)
   set_longjmp_breakpoint (tp, get_frame_id (frame));
   old_chain = make_cleanup (delete_longjmp_breakpoint_cleanup, &thread);
 
-  sm = new_until_next_fsm (tp->num);
+  sm = new_until_next_fsm (tp->global_num);
   tp->thread_fsm = &sm->thread_fsm;
   discard_cleanups (old_chain);
 
   proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT);
-
 }
 
 static void
@@ -1774,7 +1773,7 @@  finish_command_fsm_should_stop (struct thread_fsm *self)
 {
   struct finish_command_fsm *f = (struct finish_command_fsm *) self;
   struct return_value_info *rv = &f->return_value;
-  struct thread_info *tp = find_thread_id (f->thread);
+  struct thread_info *tp = find_thread_global_id (f->thread);
 
   if (f->function != NULL
       && bpstat_find_breakpoint (tp->control.stop_bpstat,
@@ -1967,7 +1966,7 @@  finish_command (char *arg, int from_tty)
 
   tp = inferior_thread ();
 
-  sm = new_finish_command_fsm (tp->num);
+  sm = new_finish_command_fsm (tp->global_num);
 
   tp->thread_fsm = &sm->thread_fsm;
 
@@ -2715,7 +2714,8 @@  attach_post_wait (char *args, int from_tty, enum attach_post_wait_mode mode)
 	    {
 	      if (ptid_get_pid (thread->ptid) == pid)
 		{
-		  if (thread->num < lowest->num)
+		  if (thread->inf->num < lowest->inf->num
+		      || thread->per_inf_num < lowest->per_inf_num)
 		    lowest = thread;
 		}
 	    }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 67ac9e8..571d26a 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -304,6 +304,9 @@  struct inferior
   /* True if the PID was actually faked by GDB.  */
   int fake_pid_p;
 
+  /* The highest thread number this inferior ever had.  */
+  int highest_thread_num;
+
   /* State of GDB control of inferior process execution.
      See `struct inferior_control_state'.  */
   struct inferior_control_state control;
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 001e7b4..b343783 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -5702,7 +5702,7 @@  handle_signal_stop (struct execution_control_state *ecs)
       context_switch (ecs->ptid);
 
       if (deprecated_context_hook)
-	deprecated_context_hook (pid_to_thread_id (ecs->ptid));
+	deprecated_context_hook (ptid_to_global_thread_id (ecs->ptid));
     }
 
   /* At this point, get hold of the now-current thread's frame.  */
@@ -7510,7 +7510,7 @@  insert_exception_resume_breakpoint (struct thread_info *tp,
 	  /* set_momentary_breakpoint_at_pc invalidates FRAME.  */
 	  frame = NULL;
 
-	  bp->thread = tp->num;
+	  bp->thread = tp->global_num;
 	  inferior_thread ()->control.exception_resume_breakpoint = bp;
 	}
     }
@@ -7547,7 +7547,7 @@  insert_exception_resume_from_probe (struct thread_info *tp,
 
   bp = set_momentary_breakpoint_at_pc (get_frame_arch (frame),
 				       handler, bp_exception_resume);
-  bp->thread = tp->num;
+  bp->thread = tp->global_num;
   inferior_thread ()->control.exception_resume_breakpoint = bp;
 }
 
diff --git a/gdb/mi/mi-cmd-var.c b/gdb/mi/mi-cmd-var.c
index e95ded7..04f326c 100644
--- a/gdb/mi/mi-cmd-var.c
+++ b/gdb/mi/mi-cmd-var.c
@@ -662,7 +662,7 @@  mi_cmd_var_update_iter (struct varobj *var, void *data_pointer)
     thread_stopped = 1;
   else
     {
-      struct thread_info *tp = find_thread_id (thread_id);
+      struct thread_info *tp = find_thread_global_id (thread_id);
 
       if (tp)
 	thread_stopped = is_stopped (tp->ptid);
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index 91ca8b8..7f42367 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -362,7 +362,7 @@  mi_new_thread (struct thread_info *t)
 
   fprintf_unfiltered (mi->event_channel, 
 		      "thread-created,id=\"%d\",group-id=\"i%d\"",
-		      t->num, inf->num);
+		      t->global_num, inf->num);
   gdb_flush (mi->event_channel);
 }
 
@@ -383,7 +383,7 @@  mi_thread_exit (struct thread_info *t, int silent)
   target_terminal_ours ();
   fprintf_unfiltered (mi->event_channel, 
 		      "thread-exited,id=\"%d\",group-id=\"i%d\"",
-		      t->num, inf->num);
+		      t->global_num, inf->num);
   gdb_flush (mi->event_channel);
 
   do_cleanups (old_chain);
@@ -617,15 +617,14 @@  mi_on_normal_stop (struct bpstats *bs, int print_frame)
 	  print_stop_event (mi->cli_uiout);
 	}
 
-      ui_out_field_int (mi_uiout, "thread-id",
-			pid_to_thread_id (inferior_ptid));
+      tp = inferior_thread ();
+      ui_out_field_int (mi_uiout, "thread-id", tp->global_num);
       if (non_stop)
 	{
 	  struct cleanup *back_to = make_cleanup_ui_out_list_begin_end 
 	    (mi_uiout, "stopped-threads");
 
-	  ui_out_field_int (mi_uiout, NULL,
-			    pid_to_thread_id (inferior_ptid));
+	  ui_out_field_int (mi_uiout, NULL, tp->global_num);
 	  do_cleanups (back_to);
 	}
       else
@@ -859,7 +858,7 @@  mi_output_running_pid (struct thread_info *info, void *arg)
   if (ptid_get_pid (*ptid) == ptid_get_pid (info->ptid))
     fprintf_unfiltered (raw_stdout,
 			"*running,thread-id=\"%d\"\n",
-			info->num);
+			info->global_num);
 
   return 0;
 }
@@ -925,7 +924,8 @@  mi_on_resume (ptid_t ptid)
       struct thread_info *ti = find_thread_ptid (ptid);
 
       gdb_assert (ti);
-      fprintf_unfiltered (raw_stdout, "*running,thread-id=\"%d\"\n", ti->num);
+      fprintf_unfiltered (raw_stdout, "*running,thread-id=\"%d\"\n",
+			  ti->global_num);
     }
 
   if (!running_result_record_printed && mi_proceeded)
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index ab8eae3..e25eedf 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -2165,7 +2165,7 @@  mi_execute_command (const char *cmd, int from_tty)
 	    {
 	      struct thread_info *ti = inferior_thread ();
 
-	      report_change = (ti->num != command->thread);
+	      report_change = (ti->global_num != command->thread);
 	    }
 
 	  if (report_change)
@@ -2175,7 +2175,7 @@  mi_execute_command (const char *cmd, int from_tty)
 	      target_terminal_ours ();
 	      fprintf_unfiltered (mi->event_channel,
 				  "thread-selected,id=\"%d\"",
-				  ti->num);
+				  ti->global_num);
 	      gdb_flush (mi->event_channel);
 	    }
 	}
@@ -2226,7 +2226,7 @@  mi_cmd_execute (struct mi_parse *parse)
 
   if (parse->thread != -1)
     {
-      struct thread_info *tp = find_thread_id (parse->thread);
+      struct thread_info *tp = find_thread_global_id (parse->thread);
 
       if (!tp)
 	error (_("Invalid thread id: %d"), parse->thread);
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 640ec1b..85b17d5 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -203,7 +203,7 @@  bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       if (! gdb_py_int_as_long (newvalue, &id))
 	return -1;
 
-      if (! valid_thread_id (id))
+      if (!valid_global_thread_id (id))
 	{
 	  PyErr_SetString (PyExc_RuntimeError,
 			   _("Invalid thread ID."));
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index 38cf293..bff6dba 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -221,7 +221,7 @@  bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
   if (PyErr_Occurred ())
     return -1;
 
-  thread = pid_to_thread_id (inferior_ptid);
+  thread = ptid_to_global_thread_id (inferior_ptid);
   if (thread == 0)
     {
       PyErr_SetString (PyExc_ValueError,
diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c
index 5075071..a9dd5cb 100644
--- a/gdb/python/py-infthread.c
+++ b/gdb/python/py-infthread.c
@@ -115,6 +115,8 @@  thpy_set_name (PyObject *self, PyObject *newvalue, void *ignore)
   return 0;
 }
 
+/* Getter for InferiorThread.num.  */
+
 static PyObject *
 thpy_get_num (PyObject *self, void *closure)
 {
@@ -122,7 +124,7 @@  thpy_get_num (PyObject *self, void *closure)
 
   THPY_REQUIRE_VALID (thread_obj);
 
-  return PyLong_FromLong (thread_obj->thread->num);
+  return PyLong_FromLong (thread_obj->thread->per_inf_num);
 }
 
 /* Getter for InferiorThread.ptid  -> (pid, lwp, tid).
@@ -294,7 +296,8 @@  static PyGetSetDef thread_object_getset[] =
 {
   { "name", thpy_get_name, thpy_set_name,
     "The name of the thread, as set by the user or the OS.", NULL },
-  { "num", thpy_get_num, NULL, "ID of the thread, as assigned by GDB.", NULL },
+  { "num", thpy_get_num, NULL,
+    "Per-inferior number of the thread, as assigned by GDB.", NULL },
   { "ptid", thpy_get_ptid, NULL, "ID of the thread, as assigned by the OS.",
     NULL },
   { "inferior", thpy_get_inferior, NULL,
diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
index 8c33010..b886fd8 100644
--- a/gdb/record-btrace.c
+++ b/gdb/record-btrace.c
@@ -218,7 +218,7 @@  record_btrace_open (const char *args, int from_tty)
 
   disable_chain = make_cleanup (null_cleanup, NULL);
   ALL_NON_EXITED_THREADS (tp)
-    if (args == NULL || *args == 0 || number_is_in_list (args, tp->num))
+    if (args == NULL || *args == 0 || number_is_in_list (args, tp->global_num))
       {
 	btrace_enable (tp, &record_btrace_conf);
 
diff --git a/gdb/remote.c b/gdb/remote.c
index 528d863..3c58130 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -3881,7 +3881,9 @@  process_initial_stop_replies (int from_tty)
 	  && thread->suspend.waitstatus_pending_p)
 	selected = thread;
 
-      if (lowest_stopped == NULL || thread->num < lowest_stopped->num)
+      if (lowest_stopped == NULL
+	  || thread->inf->num < lowest_stopped->inf->num
+	  || thread->per_inf_num < lowest_stopped->per_inf_num)
 	lowest_stopped = thread;
 
       if (non_stop)
diff --git a/gdb/target.c b/gdb/target.c
index d331fe4..a499b2f 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2157,6 +2157,8 @@  target_pre_inferior (int from_tty)
      the inferior was attached to.  */
   current_inferior ()->attach_flag = 0;
 
+  current_inferior ()->highest_thread_num = 0;
+
   agent_capability_invalidate ();
 }
 
diff --git a/gdb/testsuite/gdb.base/break.exp b/gdb/testsuite/gdb.base/break.exp
index 3ec74bf..2b4587e 100644
--- a/gdb/testsuite/gdb.base/break.exp
+++ b/gdb/testsuite/gdb.base/break.exp
@@ -550,7 +550,7 @@  gdb_test "break $bp_location12 thread 999" "Unknown thread 999.*" \
     "thread-specific breakpoint on non-existent thread disallowed"
 
 gdb_test "break $bp_location12 thread foo" \
-    "Junk after thread keyword.*" \
+    "Invalid thread ID: foo" \
     "thread-specific breakpoint on bogus thread ID disallowed"
 
 # Verify that GDB responds gracefully to a breakpoint command with
diff --git a/gdb/testsuite/gdb.base/hbreak2.exp b/gdb/testsuite/gdb.base/hbreak2.exp
index dbf55f3..3a303ec 100644
--- a/gdb/testsuite/gdb.base/hbreak2.exp
+++ b/gdb/testsuite/gdb.base/hbreak2.exp
@@ -341,7 +341,7 @@  gdb_test "hbreak $bp_location12 thread 999" "Unknown thread 999.*" \
     "thread-specific hardware breakpoint on non-existent thread disallowed"
 
 gdb_test "hbreak $bp_location12 thread foo" \
-    "Junk after thread keyword.*" \
+    "Invalid thread ID: foo" \
     "thread-specific hardware breakpoint on bogus thread ID disallowed"
 
 # Verify that GDB responds gracefully to a breakpoint command with
diff --git a/gdb/testsuite/gdb.base/sepdebug.exp b/gdb/testsuite/gdb.base/sepdebug.exp
index ad87269..3ad3669 100644
--- a/gdb/testsuite/gdb.base/sepdebug.exp
+++ b/gdb/testsuite/gdb.base/sepdebug.exp
@@ -361,7 +361,7 @@  gdb_test "break $bp_location12 thread 999" "Unknown thread 999.*" \
     "thread-specific breakpoint on non-existent thread disallowed"
 
 gdb_test "break $bp_location12 thread foo" \
-    "Junk after thread keyword.*" \
+    "Invalid thread ID: foo" \
     "thread-specific breakpoint on bogus thread ID disallowed"
 
 # Verify that GDB responds gracefully to a breakpoint command with
diff --git a/gdb/testsuite/gdb.base/watch_thread_num.exp b/gdb/testsuite/gdb.base/watch_thread_num.exp
index b3ce273..eff9fbd 100644
--- a/gdb/testsuite/gdb.base/watch_thread_num.exp
+++ b/gdb/testsuite/gdb.base/watch_thread_num.exp
@@ -46,7 +46,7 @@  if { ![runto main] } then {
    return
 }
 
-gdb_test "watch shared_var thread 0" "Unknown thread 0\." "Watchpoint on invalid thread"
+gdb_test "watch shared_var thread 0" "Invalid thread ID: 0" "Watchpoint on invalid thread"
 gdb_test "watch shared_var thread" "A syntax error in expression, near `thread'\." "Invalid watch syntax"
 
 set bpexitline [gdb_get_line_number "all threads started"]
diff --git a/gdb/testsuite/gdb.linespec/keywords.exp b/gdb/testsuite/gdb.linespec/keywords.exp
index 03f662c..11e2c4c 100644
--- a/gdb/testsuite/gdb.linespec/keywords.exp
+++ b/gdb/testsuite/gdb.linespec/keywords.exp
@@ -54,16 +54,16 @@  with_test_prefix "trailing whitespace" {
 # break {thread,task} NUMBER --> invalid thread/task
 # break {thread,task} STUFF --> "junk" after keyword (STUFF is not numeric)
 gdb_test "break thread 123" "Unknown thread 123\\."
-gdb_test "break thread foo" "Junk after thread keyword\\."
+gdb_test "break thread foo" "Invalid thread ID: foo"
 gdb_test "break task 123" "Unknown task 123\\."
 gdb_test "break task foo" "Junk after task keyword\\."
 gdb_breakpoint "thread if 0" "message"
 
 # These are also NULL locations, but using a subsequent keyword
 # as the "junk".
-gdb_test "break thread thread" "Junk after thread keyword\\."
-gdb_test "break thread task" "Junk after thread keyword\\."
-gdb_test "break thread if" "Junk after thread keyword\\."
+gdb_test "break thread thread" "Invalid thread ID: thread"
+gdb_test "break thread task" "Invalid thread ID: task"
+gdb_test "break thread if" "Invalid thread ID: if"
 gdb_test "break task task" "Junk after task keyword\\."
 gdb_test "break task thread" "Junk after task keyword\\."
 gdb_test "break task if" "Junk after task keyword\\."
diff --git a/gdb/testsuite/gdb.multi/info-threads.exp b/gdb/testsuite/gdb.multi/info-threads.exp
index 7ae60d7..f8d9c6d 100644
--- a/gdb/testsuite/gdb.multi/info-threads.exp
+++ b/gdb/testsuite/gdb.multi/info-threads.exp
@@ -36,4 +36,4 @@  gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
 
 # "info threads" while inferior 1 has execution and inferior 2 is not
 # running yet should show inferior 1's thread, and give no error.
-gdb_test "info threads" "1    .* main .* at .*$srcfile:.*No selected thread.*"
+gdb_test "info threads" "1\.1 .* main .* at .*$srcfile:.*No selected thread.*"
diff --git a/gdb/testsuite/gdb.multi/tids.c b/gdb/testsuite/gdb.multi/tids.c
new file mode 100644
index 0000000..00a8298
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/tids.c
@@ -0,0 +1,52 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2015 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 <unistd.h>
+#include <pthread.h>
+
+pthread_t child_thread[2];
+
+void *
+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)
+{
+  int i;
+
+  alarm (300);
+
+  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/tids.exp b/gdb/testsuite/gdb.multi/tids.exp
new file mode 100644
index 0000000..7b51c80
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/tids.exp
@@ -0,0 +1,296 @@ 
+# Copyright 2015 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 thread ID parsing and display.
+
+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
+}
+
+clean_restart ${testfile}
+
+if { ![runto_main] } then {
+    return -1
+}
+
+# Issue "thread apply TID_LIST p 1" and expect EXP_TID_LIST (a list of
+# thread ids) to be displayed.
+proc thread_apply {tid_list exp_tid_list {message ""}} {
+    global decimal
+    set any "\[^\r\n\]*"
+    set expected [string_to_regexp $exp_tid_list]
+
+    set r ""
+    foreach tid $expected {
+	append r "\[\r\n\]+"
+	append r "Thread $tid $any:\r\n"
+	append r "\\$$decimal = 1234"
+    }
+
+    set cmd "thread apply $tid_list"
+    if {$message == ""} {
+	set message $cmd
+    }
+    gdb_test "$cmd p 1234" $r $message
+}
+
+# Issue "info threads TID_LIST" and expect EXP_TID_LIST (a list of
+# thread ids) to be displayed.
+proc info_threads {tid_list exp_tid_list {message ""}} {
+    set any "\[^\r\n\]*"
+    set expected [string_to_regexp $exp_tid_list]
+    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
+}
+
+# Issue both "info threads TID_LIST" and expect INFO_THR output.  Then
+# issue "thread apply TID_LIST" and expect THR_APPLY output.  If
+# THR_APPLY is omitted, INFO_THR is expected instead.
+proc thr_apply_info_thr {tid_list info_thr {thr_apply ""}} {
+    if {$thr_apply == ""} {
+	set thr_apply $info_thr
+    }
+
+    info_threads $tid_list $info_thr
+    thread_apply $tid_list $thr_apply
+}
+
+# Issue both "info threads TID_LIST" and "thread apply TID_LIST" and
+# expect both commands to error out with EXP_ERROR.
+proc thr_apply_info_thr_error {tid_list exp_error}  {
+    gdb_test "info threads $tid_list" \
+	$exp_error
+
+    gdb_test "thread apply $tid_list p 1234" \
+	$exp_error \
+	"thread apply $tid_list"
+}
+
+# Issue both "info threads TID_LIST" and "thread apply TID_LIST" and
+# expect the command to error out with "Invalid thread ID: $EXPECTED".
+# EXPECTED is a literal string, not a regexp.
+proc thr_apply_info_thr_invalid {tid_list expected} {
+    set expected [string_to_regexp $expected]
+    gdb_test "info threads $tid_list" \
+	"Invalid thread ID: $expected"
+
+    gdb_test "thread apply $tid_list p 1234" \
+	"Invalid thread ID: $expected p 1234" \
+	"thread apply $tid_list"
+}
+
+# "info threads" while there's only inferior 1 should show
+# single-number thread IDs.
+with_test_prefix "single inferior" {
+    info_threads "" "1"
+
+    gdb_test "thread" "Current thread is 1 .*"
+}
+
+# "info threads" while there are multiple inferiors should show
+# qualified thread IDs.
+with_test_prefix "two inferiors" {
+    # Add another inferior.
+    gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+
+    # Now that we'd added another inferior, thread IDs now show the
+    # inferior number.
+    info_threads "" "1.1"
+
+    gdb_test "thread" "Current thread is 1\.1 .*"
+
+    gdb_test "inferior 2" "Switching to inferior 2 .*" "switch to inferior 2"
+    gdb_test "file ${binfile}" ".*" "load file in inferior 2"
+
+    runto_main
+
+    # Now that we'd added another inferior, thread IDs now show the
+    # inferior number.
+    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_function1"
+
+    gdb_continue_to_breakpoint "once"
+    gdb_test "inferior 1" "Switching to inferior 1 .*"
+    gdb_continue_to_breakpoint "twice"
+
+    info_threads "" "1.1 1.2 2.1 2.2" \
+	"info threads again"
+
+    # Confirm the convenience variable show the expected number.
+    gdb_test "p \$_thread == 2" " = 1"
+
+    # Without an explicit inferior component, GDB defaults to the
+    # current inferior.  Make sure we don't refer to a thread by
+    # 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"
+    }
+
+    thr_apply_info_thr "1 2 3" \
+	"1.1 1.2 1.3"
+
+    # Same, but with qualified thread IDs.
+    thr_apply_info_thr "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.
+    thr_apply_info_thr "1-3" \
+	"1.1 1.2 1.3"
+
+    # Same, but using a qualified range.
+    thr_apply_info_thr "1.1-3" \
+	"1.1 1.2 1.3"
+
+    # A mix of qualified and unqualified thread IDs/ranges.
+    thr_apply_info_thr "1.1 2-3" \
+	"1.1 1.2 1.3"
+
+    thr_apply_info_thr "1 1.2-3" \
+	"1.1 1.2 1.3"
+
+    # Likewise, but mix inferiors too.
+    thr_apply_info_thr "2.1 2-3" \
+	"1.2 1.3 2.1" \
+	"2.1 1.2 1.3"
+
+    # Multiple ranges with mixed explicit inferiors.
+    thr_apply_info_thr "1.1-2 2.2-3" \
+	"1.1 1.2 2.2 2.3"
+
+    # Now test a set of invalid thread IDs/ranges.
+
+    thr_apply_info_thr_invalid "1." \
+	"1."
+
+    thr_apply_info_thr_invalid "1-3 1." \
+	"1."
+
+    thr_apply_info_thr_invalid "1.1.1" \
+	"1.1.1"
+
+    thr_apply_info_thr_invalid "2 1.1.1" \
+	"1.1.1"
+
+    thr_apply_info_thr_invalid "1.1.1 2" \
+	"1.1.1 2"
+
+    thr_apply_info_thr_invalid "1-2.1" \
+	"1-2.1"
+
+    thr_apply_info_thr_error "1-0" "inverted range"
+    thr_apply_info_thr_error "1.1-0" "inverted range"
+
+    thr_apply_info_thr_error "1-" "inverted range"
+    thr_apply_info_thr_error "1.1-" "inverted range"
+
+    thr_apply_info_thr_error "2-1" "inverted range"
+    thr_apply_info_thr_error "1.2-1" "inverted range"
+
+    thr_apply_info_thr_error "-1" "negative value"
+    thr_apply_info_thr_error "1.-1" "negative value"
+
+    # Check that we do parse the inferior number and don't confuse it.
+    gdb_test "info threads 3.1" \
+	"No threads match '3.1'\."
+}
+
+if { ![skip_python_tests] } {
+    with_test_prefix "python" {
+	# Check that InferiorThread.num returns the expected number.
+	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"
+    }
+}
+
+# Remove the second inferior and confirm that GDB goes back to showing
+# single-number thread IDs.
+with_test_prefix "back to one inferior" {
+    gdb_test "kill inferior 2" "" "kill inferior 2" "Kill the program being debugged.*" "y"
+    gdb_test "thread 1.1" "Switching to thread 1\.1 .*"
+    gdb_test "remove-inferior 2" ".*" "remove inferior 2"
+
+    # "info threads" while there's only inferior 1 should show
+    # single-number thread IDs.
+    info_threads "" "1 2 3"
+
+    gdb_test "thread" "Current thread is 1 .*"
+}
+
+# Add another inferior and remove inferior 1.  Since even though
+# there's a single inferior, its number is not 1, GDB should show
+# inferior-qualified thread IDs.
+with_test_prefix "single-inferior but not initial" {
+    # Add another inferior.
+    gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior"
+
+    # Now that we'd added another inferior, thread IDs should show the
+    # inferior number.
+    info_threads "" "1.1 1.2 1.3" \
+	"info threads with multiple inferiors"
+
+    gdb_test "thread" "Current thread is 1\.1 .*"
+
+    gdb_test "inferior 3" "Switching to inferior 3 .*" "switch to inferior 3"
+    gdb_test "file ${binfile}" ".*" "load file in inferior 3"
+
+    runto_main
+
+    gdb_test "remove-inferior 1" ".*" "remove inferior 1"
+
+    # Even though we have a single inferior, its number is > 1, so
+    # thread IDs should include the inferior number.
+    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/thread-find.exp b/gdb/testsuite/gdb.threads/thread-find.exp
index bd90c57..6d885a7 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 87861d5..64002e1 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -43,6 +43,7 @@ 
 #include "gdb_regex.h"
 #include "cli/cli-utils.h"
 #include "thread-fsm.h"
+#include "tid-parse.h"
 
 /* Definition of struct thread_info exported to gdbthread.h.  */
 
@@ -182,7 +183,7 @@  clear_thread_inferior_resources (struct thread_info *tp)
   delete_at_next_stop (&tp->control.exception_resume_breakpoint);
   delete_at_next_stop (&tp->control.single_step_breakpoints);
 
-  delete_longjmp_breakpoint_at_next_stop (tp->num);
+  delete_longjmp_breakpoint_at_next_stop (tp->global_num);
 
   bpstat_clear (&tp->control.stop_bpstat);
 
@@ -226,16 +227,22 @@  init_thread_list (void)
   threads_executing = 0;
 }
 
-/* Allocate a new thread with target id PTID and add it to the thread
-   list.  */
+/* Allocate a new thread of inferior INF with target id PTID and add
+   it to the thread list.  */
 
 static struct thread_info *
-new_thread (ptid_t ptid)
+new_thread (struct inferior *inf, ptid_t ptid)
 {
-  struct thread_info *tp = XCNEW (struct thread_info);
+  struct thread_info *tp;
+
+  gdb_assert (inf != NULL);
+
+  tp = XCNEW (struct thread_info);
 
   tp->ptid = ptid;
-  tp->num = ++highest_thread_num;
+  tp->global_num = ++highest_thread_num;
+  tp->inf = inf;
+  tp->per_inf_num = ++inf->highest_thread_num;
 
   if (thread_list == NULL)
     thread_list = tp;
@@ -260,6 +267,8 @@  struct thread_info *
 add_thread_silent (ptid_t ptid)
 {
   struct thread_info *tp;
+  struct inferior *inf = find_inferior_ptid (ptid);
+  gdb_assert (inf != NULL);
 
   tp = find_thread_ptid (ptid);
   if (tp)
@@ -277,7 +286,7 @@  add_thread_silent (ptid_t ptid)
 
       if (ptid_equal (inferior_ptid, ptid))
 	{
-	  tp = new_thread (null_ptid);
+	  tp = new_thread (inf, null_ptid);
 
 	  /* Make switch_to_thread not read from the thread.  */
 	  tp->state = THREAD_EXITED;
@@ -301,7 +310,7 @@  add_thread_silent (ptid_t ptid)
 	delete_thread (ptid);
     }
 
-  tp = new_thread (ptid);
+  tp = new_thread (inf, ptid);
   observer_notify_new_thread (tp);
 
   return tp;
@@ -481,12 +490,24 @@  delete_thread_silent (ptid_t ptid)
 }
 
 struct thread_info *
-find_thread_id (int num)
+find_thread_global_id (int global_id)
 {
   struct thread_info *tp;
 
   for (tp = thread_list; tp; tp = tp->next)
-    if (tp->num == num)
+    if (tp->global_num == global_id)
+      return tp;
+
+  return NULL;
+}
+
+static struct thread_info *
+find_thread_id (struct inferior *inf, int thr_num)
+{
+  struct thread_info *tp;
+
+  for (tp = thread_list; tp; tp = tp->next)
+    if (tp->inf == inf && tp->per_inf_num == thr_num)
       return tp;
 
   return NULL;
@@ -548,38 +569,38 @@  thread_count (void)
 }
 
 int
-valid_thread_id (int num)
+valid_global_thread_id (int global_id)
 {
   struct thread_info *tp;
 
   for (tp = thread_list; tp; tp = tp->next)
-    if (tp->num == num)
+    if (tp->global_num == global_id)
       return 1;
 
   return 0;
 }
 
 int
-pid_to_thread_id (ptid_t ptid)
+ptid_to_global_thread_id (ptid_t ptid)
 {
   struct thread_info *tp;
 
   for (tp = thread_list; tp; tp = tp->next)
     if (ptid_equal (tp->ptid, ptid))
-      return tp->num;
+      return tp->global_num;
 
   return 0;
 }
 
 ptid_t
-thread_id_to_pid (int num)
+global_thread_id_to_ptid (int global_id)
 {
-  struct thread_info *thread = find_thread_id (num);
+  struct thread_info *thread = find_thread_global_id (global_id);
 
   if (thread)
     return thread->ptid;
   else
-    return pid_to_ptid (-1);
+    return minus_one_ptid;
 }
 
 int
@@ -604,7 +625,7 @@  first_thread_of_process (int pid)
 
   for (tp = thread_list; tp; tp = tp->next)
     if (pid == -1 || ptid_get_pid (tp->ptid) == pid)
-      if (ret == NULL || tp->num < ret->num)
+      if (ret == NULL || tp->global_num < ret->global_num)
 	ret = tp;
 
   return ret;
@@ -688,10 +709,10 @@  do_captured_list_thread_ids (struct ui_out *uiout, void *arg)
 	continue;
 
       if (ptid_equal (tp->ptid, inferior_ptid))
-	current_thread = tp->num;
+	current_thread = tp->global_num;
 
       num++;
-      ui_out_field_int (uiout, "thread-id", tp->num);
+      ui_out_field_int (uiout, "thread-id", tp->global_num);
     }
 
   do_cleanups (cleanup_chain);
@@ -1105,25 +1126,62 @@  pc_in_thread_step_range (CORE_ADDR pc, struct thread_info *thread)
 	  && pc < thread->control.step_range_end);
 }
 
-/* Prints the list of threads and their details on UIOUT.
-   This is a version of 'info_threads_command' suitable for
-   use from MI.
-   If REQUESTED_THREAD is not -1, it's the GDB id of the thread
-   that should be printed.  Otherwise, all threads are
-   printed.
-   If PID is not -1, only print threads from the process PID.
-   Otherwise, threads from all attached PIDs are printed.
-   If both REQUESTED_THREAD and PID are not -1, then the thread
-   is printed if it belongs to the specified process.  Otherwise,
-   an error is raised.  */
-void
-print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
+/* Helper for print_thread_info.  Returns true if THR should be
+   printed.  If REQUESTED_THREADS, a list of GDB ids/ranges, is not
+   NULL, only print THR if its ID is included in the list.  GLOBAL_IDS
+   is true if REQUESTED_THREADS is list of global IDs, false if a list
+   of per-inferior thread ids.  If PID is not -1, only print THR if it
+   is a thread from the process PID.  Otherwise, threads from all
+   attached PIDs are printed.  If both REQUESTED_THREADS is not NULL
+   and PID is not -1, then the thread is printed if it belongs to the
+   specified process.  Otherwise, an error is raised.  */
+
+static int
+should_print_thread (const char *requested_threads, int default_inf_num,
+		     int global_ids, int pid, struct thread_info *thr)
+{
+  if (requested_threads != NULL && *requested_threads != '\0')
+    {
+      int in_list;
+
+      if (global_ids)
+	in_list = number_is_in_list (requested_threads, thr->global_num);
+      else
+	in_list = tid_is_in_list (requested_threads, default_inf_num,
+				  thr->inf->num, thr->per_inf_num);
+      if (!in_list)
+	return 0;
+    }
+
+  if (pid != -1 && ptid_get_pid (thr->ptid) != pid)
+    {
+      if (requested_threads != NULL && *requested_threads != '\0')
+	error (_("Requested thread not found in requested process"));
+      return 0;
+    }
+
+  if (thr->state == THREAD_EXITED)
+    return 0;
+
+  return 1;
+}
+
+/* Like print_thread_info, but in addition, GLOBAL_IDS indicates
+   whether REQUESTED_THREADS is a list of global or per-inferior
+   thread ids.  */
+
+static void
+print_thread_info_1 (struct ui_out *uiout, char *requested_threads,
+		     int global_ids, int pid,
+		     int show_global_ids)
 {
   struct thread_info *tp;
   ptid_t current_ptid;
   struct cleanup *old_chain;
   const char *extra_info, *name, *target_id;
   int current_thread = -1;
+  struct inferior *inf;
+  int default_inf_num = current_inferior ()->num;
 
   update_thread_list ();
   current_ptid = inferior_ptid;
@@ -1142,13 +1200,8 @@  print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
 
       for (tp = thread_list; tp; tp = tp->next)
 	{
-	  if (!number_is_in_list (requested_threads, tp->num))
-	    continue;
-
-	  if (pid != -1 && ptid_get_pid (tp->ptid) != pid)
-	    continue;
-
-	  if (tp->state == THREAD_EXITED)
+	  if (!should_print_thread (requested_threads, default_inf_num,
+				    global_ids, pid, tp))
 	    continue;
 
 	  ++n_threads;
@@ -1165,34 +1218,32 @@  print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
 	  return;
 	}
 
-      make_cleanup_ui_out_table_begin_end (uiout, 4, n_threads, "threads");
+      if (show_global_ids || ui_out_is_mi_like_p (uiout))
+	make_cleanup_ui_out_table_begin_end (uiout, 5, n_threads, "threads");
+      else
+	make_cleanup_ui_out_table_begin_end (uiout, 4, n_threads, "threads");
 
       ui_out_table_header (uiout, 1, ui_left, "current", "");
-      ui_out_table_header (uiout, 4, ui_left, "id", "Id");
+
+      if (!ui_out_is_mi_like_p (uiout))
+	ui_out_table_header (uiout, 4, ui_left, "id-in-tg", "Id");
+      if (show_global_ids || ui_out_is_mi_like_p (uiout))
+	ui_out_table_header (uiout, 4, ui_left, "id", "GId");
       ui_out_table_header (uiout, 17, ui_left, "target-id", "Target Id");
       ui_out_table_header (uiout, 1, ui_left, "frame", "Frame");
       ui_out_table_body (uiout);
     }
 
-  for (tp = thread_list; tp; tp = tp->next)
+  ALL_THREADS_BY_INFERIOR (inf, tp)
     {
       struct cleanup *chain2;
       int core;
 
-      if (!number_is_in_list (requested_threads, tp->num))
-	continue;
-
-      if (pid != -1 && ptid_get_pid (tp->ptid) != pid)
-	{
-	  if (requested_threads != NULL && *requested_threads != '\0')
-	    error (_("Requested thread not found in requested process"));
-	  continue;
-	}
-
       if (ptid_equal (tp->ptid, current_ptid))
-	current_thread = tp->num;
+	current_thread = tp->global_num;
 
-      if (tp->state == THREAD_EXITED)
+      if (!should_print_thread (requested_threads, default_inf_num,
+				global_ids, pid, tp))
 	continue;
 
       chain2 = make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
@@ -1213,7 +1264,11 @@  print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
 	    ui_out_field_skip (uiout, "current");
 	}
 
-      ui_out_field_int (uiout, "id", tp->num);
+      if (!ui_out_is_mi_like_p (uiout))
+	ui_out_field_string (uiout, "id-in-tg", print_thread_id (tp));
+
+      if (show_global_ids || ui_out_is_mi_like_p (uiout))
+	ui_out_field_int (uiout, "id", tp->global_num);
 
       /* For the CLI, we stuff everything into the target-id field.
 	 This is a gross hack to make the output come out looking
@@ -1289,27 +1344,35 @@  print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
 
   if (pid == -1 && requested_threads == NULL)
     {
-      gdb_assert (current_thread != -1
-		  || !thread_list
-		  || ptid_equal (inferior_ptid, null_ptid));
-      if (current_thread != -1 && ui_out_is_mi_like_p (uiout))
-	ui_out_field_int (uiout, "current-thread-id", current_thread);
+      if (ui_out_is_mi_like_p (uiout)
+	  && !ptid_equal (inferior_ptid, null_ptid))
+	{
+	  int num = ptid_to_global_thread_id (inferior_ptid);
+
+	  gdb_assert (num != 0);
+	  ui_out_field_int (uiout, "current-thread-id", num);
+	}
 
-      if (current_thread != -1 && is_exited (current_ptid))
+      if (!ptid_equal (inferior_ptid, null_ptid) && is_exited (inferior_ptid))
 	ui_out_message (uiout, 0, "\n\
-The current thread <Thread ID %d> has terminated.  See `help thread'.\n",
-			current_thread);
-      else if (thread_list
-	       && current_thread == -1
-	       && ptid_equal (current_ptid, null_ptid))
+The current thread <Thread ID %s> has terminated.  See `help thread'.\n",
+			print_thread_id (inferior_thread ()));
+      else if (thread_list != NULL
+	       && ptid_equal (inferior_ptid, null_ptid))
 	ui_out_message (uiout, 0, "\n\
 No selected thread.  See `help thread'.\n");
     }
 }
 
-/* Print information about currently known threads 
+/* See gdbthread.h.  */
 
-   Optional ARG is a thread id, or list of thread ids.
+void
+print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
+{
+  print_thread_info_1 (uiout, requested_threads, 1, pid, 0);
+}
+
+/* Implementation of the "info threads" command.
 
    Note: this has the drawback that it _really_ switches
          threads, which frees the frame cache.  A no-side
@@ -1318,7 +1381,7 @@  No selected thread.  See `help thread'.\n");
 static void
 info_threads_command (char *arg, int from_tty)
 {
-  print_thread_info (current_uiout, arg, -1);
+  print_thread_info_1 (current_uiout, arg, 0, -1, 0);
 }
 
 /* See gdbthread.h.  */
@@ -1590,12 +1653,23 @@  make_cleanup_restore_current_thread (void)
 
 /* See gdbthread.h.  */
 
+int
+show_inferior_qualified_tids (void)
+{
+  return (inferior_list->next != NULL || inferior_list->num != 1);
+}
+
+/* See gdbthread.h.  */
+
 const char *
 print_thread_id (struct thread_info *thr)
 {
   char *s = get_print_cell ();
 
-  xsnprintf (s, PRINT_CELL_SIZE, "%d", thr->num);
+  if (show_inferior_qualified_tids ())
+    xsnprintf (s, PRINT_CELL_SIZE, "%d.%d", thr->inf->num, thr->per_inf_num);
+  else
+    xsnprintf (s, PRINT_CELL_SIZE, "%d", thr->per_inf_num);
   return s;
 }
 
@@ -1604,18 +1678,24 @@  print_thread_id (struct thread_info *thr)
 
 static int tp_array_compar_ascending;
 
-/* Sort an array for struct thread_info pointers by their NUM, order is
-   determined by TP_ARRAY_COMPAR_ASCENDING.  */
+/* Sort an array for struct thread_info pointers by thread ID (first
+   by inferior number, and then by per-inferior thread number).  The
+   order is determined by TP_ARRAY_COMPAR_ASCENDING.  */
 
 static int
 tp_array_compar (const void *ap_voidp, const void *bp_voidp)
 {
-  const struct thread_info *const *ap
-    = (const struct thread_info * const*) ap_voidp;
-  const struct thread_info *const *bp
-    = (const struct thread_info * const*) bp_voidp;
+  const struct thread_info *a = *(const struct thread_info * const *) ap_voidp;
+  const struct thread_info *b = *(const struct thread_info * const *) bp_voidp;
+
+  if (a->inf->num != b->inf->num)
+    {
+      return ((a->inf->num > b->inf->num) - (a->inf->num < b->inf->num)
+	      * (tp_array_compar_ascending ? +1 : -1));
+    }
 
-  return ((((*ap)->num > (*bp)->num) - ((*ap)->num < (*bp)->num))
+  return (((a->per_inf_num > b->per_inf_num)
+	   - (a->per_inf_num < b->per_inf_num))
 	  * (tp_array_compar_ascending ? +1 : -1));
 }
 
@@ -1710,7 +1790,7 @@  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"));
@@ -1725,33 +1805,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 (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 (show_inferior_qualified_tids ()
+	      || 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 %d (%s):\n"), tp->num,
-			   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);
@@ -1872,30 +1963,42 @@  show_print_thread_events (struct ui_file *file, int from_tty,
 }
 
 static int
-do_captured_thread_select (struct ui_out *uiout, void *tidstr)
+do_captured_thread_select (struct ui_out *uiout, void *tidstr_v)
 {
-  int num;
+  const char *tidstr = tidstr_v;
   struct thread_info *tp;
 
-  num = value_as_long (parse_and_eval ((const char *) tidstr));
-
-  tp = find_thread_id (num);
+  if (ui_out_is_mi_like_p (uiout))
+    {
+      int num = value_as_long (parse_and_eval (tidstr));
 
-  if (!tp)
-    error (_("Thread ID %d not known."), num);
+      tp = find_thread_global_id (num);
+      if (tp == NULL)
+	error (_("Thread ID %d not known."), num);
+    }
+  else
+    {
+      tp = parse_thread_id (tidstr, NULL);
+      gdb_assert (tp != NULL);
+    }
 
   if (!thread_alive (tp))
-    error (_("Thread ID %d has terminated."), num);
+    error (_("Thread ID %s has terminated."), tidstr);
 
   switch_to_thread (tp->ptid);
 
   annotate_thread_changed ();
 
-  ui_out_text (uiout, "[Switching to thread ");
-  ui_out_field_string (uiout, "new-thread-id", print_thread_id (tp));
-  ui_out_text (uiout, " (");
-  ui_out_text (uiout, target_pid_to_str (inferior_ptid));
-  ui_out_text (uiout, ")]");
+  if (ui_out_is_mi_like_p (uiout))
+    ui_out_field_int (uiout, "new-thread-id", inferior_thread ()->global_num);
+  else
+    {
+      ui_out_text (uiout, "[Switching to thread ");
+      ui_out_field_string (uiout, "new-thread-id", print_thread_id (tp));
+      ui_out_text (uiout, " (");
+      ui_out_text (uiout, target_pid_to_str (inferior_ptid));
+      ui_out_text (uiout, ")]");
+    }
 
   /* Note that we can't reach this with an exited thread, due to the
      thread_alive check above.  */
@@ -1949,8 +2052,9 @@  update_thread_list (void)
   update_threads_executing ();
 }
 
-/* Return a new value for the selected thread's id.  Return a value of 0 if
-   no thread is selected, or no threads exist.  */
+/* Return a new value for the selected thread's per-inferior thread
+   number.  Return a value of 0 if no thread is selected, or no
+   threads exist.  */
 
 static struct value *
 thread_id_make_value (struct gdbarch *gdbarch, struct internalvar *var,
@@ -1959,7 +2063,7 @@  thread_id_make_value (struct gdbarch *gdbarch, struct internalvar *var,
   struct thread_info *tp = find_thread_ptid (inferior_ptid);
 
   return value_from_longest (builtin_type (gdbarch)->builtin_int,
-			     (tp ? tp->num : 0));
+			     (tp ? tp->per_inf_num : 0));
 }
 
 /* Commands with a prefix of `thread'.  */
@@ -1982,8 +2086,8 @@  _initialize_thread (void)
   add_info ("threads", info_threads_command, 
 	    _("Display currently known threads.\n\
 Usage: info threads [ID]...\n\
-Optional arguments are thread IDs with spaces between.\n\
-If no arguments, all threads are displayed."));
+If ID is given, it is a space-separated list of IDs of threads to display.\n\
+Otherwise, all threads are displayed."));
 
   add_prefix_cmd ("thread", class_run, thread_command, _("\
 Use this command to switch between threads.\n\
diff --git a/gdb/tid-parse.c b/gdb/tid-parse.c
new file mode 100644
index 0000000..7c30733
--- /dev/null
+++ b/gdb/tid-parse.c
@@ -0,0 +1,267 @@ 
+/* TID parsing for GDB, the GNU debugger.
+
+   Copyright (C) 2015-2016 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 "defs.h"
+#include "tid-parse.h"
+#include "inferior.h"
+#include "gdbthread.h"
+#include <ctype.h>
+
+/* See tid-parse.h.  */
+
+void ATTRIBUTE_NORETURN
+invalid_thread_id_error (const char *string)
+{
+  error (_("Invalid thread ID: %s"), string);
+}
+
+/* See tid-parse.h.  */
+
+struct thread_info *
+parse_thread_id (const char *tidstr, const char **end)
+{
+  const char *number = tidstr;
+  const char *dot, *p1;
+  struct thread_info *tp;
+  struct inferior *inf;
+  int thr_num;
+  int explicit_inf_id = 0;
+
+  dot = strchr (number, '.');
+
+  if (dot != NULL)
+    {
+      /* Parse number to the left of the dot.  */
+      int inf_num;
+
+      p1 = number;
+      inf_num = get_number_trailer (&p1, '.');
+      if (inf_num == 0)
+	invalid_thread_id_error (number);
+
+      inf = find_inferior_id (inf_num);
+      if (inf == NULL)
+	error (_("No inferior number '%d'"), inf_num);
+
+      explicit_inf_id = 1;
+      p1 = dot + 1;
+    }
+  else
+    {
+      inf = current_inferior ();
+
+      p1 = number;
+    }
+
+  thr_num = get_number_const (&p1);
+  if (thr_num == 0)
+    invalid_thread_id_error (number);
+
+  ALL_THREADS (tp)
+    {
+      if (ptid_get_pid (tp->ptid) == inf->pid
+	  && tp->per_inf_num == thr_num)
+	break;
+    }
+
+  if (tp == NULL)
+    {
+      if (show_inferior_qualified_tids () || explicit_inf_id)
+	error (_("Unknown thread %d.%d."), inf->num, thr_num);
+      else
+	error (_("Unknown thread %d."), thr_num);
+    }
+
+  if (end != NULL)
+    *end = p1;
+
+  return tp;
+}
+
+/* See tid-parse.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 tid-parse.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 tid-parse.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 tid-parse.h.  */
+
+void
+tid_range_parser_skip (struct tid_range_parser *parser)
+{
+  gdb_assert ((parser->state == TID_RANGE_STATE_THREAD_RANGE)
+	      && parser->range_parser.in_range);
+
+  tid_range_parser_init (parser, parser->range_parser.end_ptr,
+			 parser->default_inferior);
+}
+
+/* See tid-parse.h.  */
+
+int
+tid_range_parser_qualified (struct tid_range_parser *parser)
+{
+  return parser->qualified;
+}
+
+/* See tid-parse.h.  */
+
+int
+tid_range_parser_get_tid_range (struct tid_range_parser *parser, int *inf_num,
+				int *thr_start, int *thr_end)
+{
+  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)
+	    invalid_thread_id_error (parser->string);
+
+	  parser->qualified = 1;
+	  p = dot + 1;
+
+	  if (isspace (*p))
+	    invalid_thread_id_error (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_start = get_number_or_range (&parser->range_parser);
+  if (*thr_start == 0)
+    invalid_thread_id_error (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;
+
+      if (thr_end != NULL)
+	*thr_end = *thr_start;
+    }
+
+  /* If we're midway through a range, and the caller wants the end
+     value, return it and skip to the end of the range.  */
+  if (thr_end != NULL && parser->state == TID_RANGE_STATE_THREAD_RANGE)
+    {
+      *thr_end = parser->range_parser.end_value;
+      tid_range_parser_skip (parser);
+    }
+
+  return (*inf_num != 0 && *thr_start != 0);
+}
+
+/* See tid-parse.h.  */
+
+void
+tid_range_parser_get_tid (struct tid_range_parser *parser,
+			  int *inf_num, int *thr_num)
+{
+  tid_range_parser_get_tid_range (parser, inf_num, thr_num, NULL);
+}
+
+/* See tid-parse.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_start, tmp_thr_end;
+
+      if (!tid_range_parser_get_tid_range (&parser, &tmp_inf,
+					   &tmp_thr_start, &tmp_thr_end))
+	invalid_thread_id_error (parser.string);
+      if (tmp_inf == inf_num
+	  && tmp_thr_start <= thr_num && thr_num <= tmp_thr_end)
+	return 1;
+    }
+  return 0;
+}
diff --git a/gdb/tid-parse.h b/gdb/tid-parse.h
new file mode 100644
index 0000000..3c79c08
--- /dev/null
+++ b/gdb/tid-parse.h
@@ -0,0 +1,148 @@ 
+/* TID parsing for GDB, the GNU debugger.
+   Copyright (C) 2015-2016 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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/>.  */
+
+#ifndef TID_PARSE_H
+#define TID_PARSE_H
+
+#include "cli/cli-utils.h"
+
+struct thread_info;
+
+/* Issue an invalid thread ID error, pointing at STRING, the invalid
+   ID.  */
+extern void ATTRIBUTE_NORETURN invalid_thread_id_error (const char *string);
+
+/* Parse TIDSTR as a per-inferior thread ID, in either INF_NUM.THR_NUM
+   or THR_NUM form.  In the latter case, the missing INF_NUM is filled
+   in from the current inferior.  If ENDPTR is not NULL,
+   parse_thread_id stores the address of the first character after the
+   thread ID.  Either a valid thread is returned, or an error is
+   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);
+
+/* Like tid_range_parser_get_tid, but return a thread range per call,
+   rather then a single thread.
+
+   If the next element in the list is a single thread ID, then
+   *THR_START and *THR_END are set to the same value.  E.g.:
+
+     list: "1.2 3.4-6"
+     1st call: *INF_NUM=1; *THR_START=2; *THR_END=2
+     2nd call: *INF_NUM=3; *THR_START=4; *THR_END=4
+
+
+   Returns true if parsed a thread/range successfully, false
+   otherwise.  */
+extern int tid_range_parser_get_tid_range (struct tid_range_parser *parser,
+					   int *inf_num,
+					   int *thr_start, int *thr_end);
+
+/* 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);
+
+/* When parsing a range, advance past the final token in the range.  */
+extern void tid_range_parser_skip (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);
+
+#endif /* TID_PARSE_H */
diff --git a/gdb/varobj.c b/gdb/varobj.c
index d47bdc2..8116a5b 100644
--- a/gdb/varobj.c
+++ b/gdb/varobj.c
@@ -78,7 +78,7 @@  struct varobj_root
      not NULL.  */
   struct frame_id frame;
 
-  /* The thread ID that this varobj_root belong to.  This field
+  /* The global thread ID that this varobj_root belongs to.  This field
      is only valid if valid_block is not NULL.
      When not 0, indicates which thread 'frame' belongs to.
      When 0, indicates that the thread list was empty when the varobj_root
@@ -380,7 +380,7 @@  varobj_create (char *objname,
 	    error (_("Failed to find the specified frame"));
 
 	  var->root->frame = get_frame_id (fi);
-	  var->root->thread_id = pid_to_thread_id (inferior_ptid);
+	  var->root->thread_id = ptid_to_global_thread_id (inferior_ptid);
 	  old_id = get_frame_id (get_selected_frame (NULL));
 	  select_frame (fi);	 
 	}
@@ -2363,8 +2363,9 @@  value_of_root_1 (struct varobj **var_handle)
     }
   else
     {
-      ptid_t ptid = thread_id_to_pid (var->root->thread_id);
-      if (in_thread_list (ptid))
+      ptid_t ptid = global_thread_id_to_ptid (var->root->thread_id);
+
+      if (!ptid_equal (minus_one_ptid, ptid))
 	{
 	  switch_to_thread (ptid);
 	  within_scope = check_scope (var);