diff mbox

Per-inferior thread IDs

Message ID 1450206316-25680-1-git-send-email-palves@redhat.com
State New
Headers show

Commit Message

Pedro Alves Dec. 15, 2015, 7:05 p.m. UTC
Currently, if you're debugging multiple inferiors, because all threads
of all inferiors share the same global number space, things get quite
confusing.  For example, with:

 (gdb) info inferiors
   Num  Description       Executable
   1    process 6022     /home/pedro/gdb/tests/threads
 * 2    process 6037     /home/pedro/gdb/tests/threads
 (gdb)
   1    Thread 0x7ffff7fc2740 (LWP 6022) "threads" (running)
   3    Thread 0x7ffff77c0700 (LWP 6028) "threads" (running)
   7    Thread 0x7ffff7fc2740 (LWP 6032) "threads" (running)
   11   Thread 0x7ffff7fc1700 (LWP 6037) "threads" (running)
   12   Thread 0x7ffff77c0700 (LWP 6038) "threads" (running)
 * 13   Thread 0x7ffff7fc2740 (LWP 6039) "threads" (running)
 (gdb)

... there's no way to tell which threads in that list belong to each
process.

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 ID.  The result is this:

 (gdb) info inferiors
   Num  Description       Executable
   1    process 8155     /home/pedro/gdb/tests/threads
 * 2    process 8156     /home/pedro/gdb/tests/threads
 (gdb) info threads
   Id   Target Id         Frame
   1.1  Thread 0x7ffff7fc2740 (LWP 8155) "threads" (running)
   1.2  Thread 0x7ffff7fc1700 (LWP 8168) "threads" (running)
   1.3  Thread 0x7ffff77c0700 (LWP 8172) "threads" (running)
   2.1  Thread 0x7ffff7fc2740 (LWP 8156) "threads" (running)
 * 3.1  Thread 0x7ffff7fc2740 (LWP 8157) "threads" (running)
   3.2  Thread 0x7ffff7fc1700 (LWP 8165) "threads" (running)
   3.3  Thread 0x7ffff77c0700 (LWP 8171) "threads" (running)
 (gdb)

And:

 (gdb) thread 1.1
 [Switching to thread 1.1 (Thread 0x7ffff7fc2740 (LWP 8155))](running)
 (gdb)

etc.

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

This raises the question of what to do with MI thread IDs.  At least
Eclipse assumes that MI thread IDs are integers rather than text, so
we can't just simply start reporting "1.2" etc. as MI thread IDs.

My idea to address this is to keep giving threads a global identifier,
_in addition_ to the per-inferior number, and make MI always refer to
the global thread IDs.

A similar problem exists with Python and Guile's API, and also with
the $_thread convenience variable.  An earlier version of this patch
kept all these referring to the global ID.  But my issue with that the
APIs ends up stuck with a confusing detail forever.  In this revision
I've instead bound $_thread and Python's InferiorThread.num to the
per-inferior thread number.  It's a backward compatibility break that
only matters when debugging multiple inferiors, so maybe worth doing.

However, there's one entry point in the APIs that I left referring to
global thread ids -- That's Python's Breakpoint.thread and Guile's
breakpoint-thread/set-breakpoint-thread breakpoint methods.

With all this in mind, I've added:

 - a new $_inferior convenience variable, that is like $_thread, but
   holds the current inferior number.

 - a new $_gthread convenience variable, that is like $_thread, but
   holds the current thread's global thread id.

 - For Python, I added a new InferiorThread.global_num attribute.

 - I also noticed that there's no way to refer to the InferiorThread's
   inferior, to get at the inferior number, so I added a new
   InferiorThread.inferior attribute too.

 - I've made the global thread IDs visible with "info thread -gid":

 (gdb) info threads  -gid
   Id   GId  Target Id         Frame
   1.1  1    Thread 0x7ffff7fc1740 (LWP 15190) "threads" (running)
   1.2  3    Thread 0x7ffff7fc0700 (LWP 15249) "threads" (running)
   1.3  4    Thread 0x7ffff77bf700 (LWP 15251) "threads" (running)
   2.1  2    Thread 0x7ffff7fc1740 (LWP 15194) "threads" (running)
   2.2  5    Thread 0x7ffff7fc0700 (LWP 15250) "threads" (running)
 * 2.3  6    Thread 0x7ffff77bf700 (LWP 15252) "threads" (running)
 (gdb)

To avoid potencially confusing users (which sneakily also avoids
updating 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)

The patch is regression free on x86_64 Fedora 20.  I have a couple
FIXMEs in there to address and a few functions are missing comments,
but nothing major, I think.  (Assuming the direction is agreed, that
is.)

However, before I dig deeper, I thought I'd post this for feedback.

I've pushed this to the users/palves/thread-ids-per-inferior branch
for convenience.

(My WIP I/T sets branch relies quite heavily on per-inferior IDs;
that's actually where the original motivation comes from.)

gdb/ChangeLog:
2015-12-15  Pedro Alves  <palves@redhat.com>

	* NEWS: Mention that thread IDs are now per inferior.
	* ada-tasks.c: Adjust to use ptid_to_global_thread_id.
	* breakpoint.c (insert_breakpoint_locations)
	(remove_threaded_breakpoints, bpstat_check_breakpoint_conditions):
	Adjust to use global IDs.
	(print_one_breakpoint_location): Use print_thread_id.
	(set_longjmp_breakpoint, check_longjmp_breakpoint_for_call_dummy)
	(set_momentary_breakpoint):
	(invalid_thread_id_error): Delete.
	(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.
	* btrace.c (btrace_enable, btrace_disable, btrace_teardown)
	(btrace_fetch, btrace_clear): Use print_thread_id.
	* cli/cli-utils.c (get_number_trailer): Make extern.
	* cli/cli-utils.h (get_number_trailer): Declare.
	(get_number_const): Adjust documentation.
	* common/print-utils.c (CELLSIZE): Delete.
	(get_cell): Rename to ...
	(get_print_cell): ... this and made extern.  Adjust call callers.
	Adjust to use PRINT_CELL_SIZE.
	* common/print-utils.h (get_print_cell): Declare.
	(PRINT_CELL_SIZE): New.
	* 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_id.  Add new fields 'per_inf_id' 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.
	(print_thread_id): Declare.
	(valid_thread_id): Rename to ...
	(valid_global_thread_id): ... this.
	(parse_thread_id): Declare.
	(find_thread_id): Rename to ...
	(find_thread_global_id): ... this.
	(ALL_THREADS): Declare.
	(print_thread_info): Add comment.
	* guile/scm-breakpoint.c (gdbscm_set_breakpoint_thread_x): Adjust
	to use the global thread ID.
	* infcmd.c (step_command_fsm_prepare)
	(step_command_fsm_should_stop): Adjust to use the global thread
	ID.
	(signal_command): Use print_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.c (should_print_inferior): New.
	(print_inferior): Use should_print_inferior and print_thread_id.
	(inferior_id_make_value): New.
	(inferior_funcs): New.
	(_initialize_inferior): Create $_inferior variable.
	* inferior.h (struct inferior) <highest_thread_num>: New field.
	* infrun.c (handle_signal_stop)
	(insert_exception_resume_breakpoint)
	(insert_exception_resume_from_probe)
	(print_signal_received_reason): Adjust to use global thread IDs.
	* 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-main.c (mi_execute_command, mi_cmd_execute): 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.
	(thpy_get_num): Return the per-inferior thread ID.
	(thpy_get_inferior): New.
	(thread_object_getset): Register "inferior".  Update comment of
	"num".
	* record-btrace.c (record_btrace_open): Use global thread IDs.
	(record_btrace_info, record_btrace_resume_thread)
	(record_btrace_cancel_resume, record_btrace_step_thread)
	(record_btrace_wait): Use print_thread_id.
	* 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.  Use print_thread_info.  Iterate over
	inferiors.
	(print_thread_info): Reimplement as wrapper around
	print_thread_info_1.
	(info_threads_command): Handle "-gid".
	(tp_array_compar): Compare inferior numbers too.
	(thread_apply_all_command): Use print_thread_id.
	(print_thread_id): New function.
	(thread_apply_command, thread_command, thread_find_command): Use
	print_thread_id.
	(parse_thread_id): New function.
	(do_captured_thread_select): Use it.
	(thread_id_make_value): Adjust.
	(_initialize_thread): Adjust "info threads" help string to mention
	-gid.
	* 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.

gdb/testsuite/ChangeLog:

	* gdb.base/break.exp: Adjust.
	* gdb.base/default.exp: Expect $_inferior and $_gthread as well.
	* gdb.base/hbreak2.exp: Adjust.
	* gdb.base/sepdebug.exp:
	gdb.base/watch_thread_num.exp
	* gdb.linespec/keywords.exp
	* gdb.multi/base.exp: Test $_inferior.
	gdb.multi/info-threads.exp: Adjust.
	* gdb.multi/per-inferior-tids.c: New file.
	* gdb.multi/per-inferior-tids.exp: New file.
	* gdb.python/py-infthread.exp: Test InferiorThread.global_num.
	Fix typo.  Expect t0.num to be 1.  Test InferiorThread.inferior.
	* gdb.threads/thread-specific.exp: Test $_gthread.

gdb/doc/ChangeLog:

	* gdb.texinfo (Inferiors and Programs): Document $_inferior
	convenience variable.
	(Threads): Document per-inferior 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.
---
 gdb/NEWS                                      |  47 +++
 gdb/ada-tasks.c                               |   2 +-
 gdb/breakpoint.c                              |  66 ++--
 gdb/btrace.c                                  |  15 +-
 gdb/cli/cli-utils.c                           |  11 +-
 gdb/cli/cli-utils.h                           |  15 +-
 gdb/common/print-utils.c                      |  70 ++---
 gdb/common/print-utils.h                      |   4 +
 gdb/doc/gdb.texinfo                           | 155 +++++++---
 gdb/doc/guile.texi                            |  11 +-
 gdb/doc/python.texi                           |  12 +-
 gdb/dummy-frame.c                             |   2 +-
 gdb/elfread.c                                 |   2 +-
 gdb/gdbthread.h                               |  68 ++++-
 gdb/guile/scm-breakpoint.c                    |   8 +-
 gdb/infcmd.c                                  |  24 +-
 gdb/inferior.c                                |  38 ++-
 gdb/inferior.h                                |   2 +
 gdb/infrun.c                                  |   8 +-
 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                     |  35 ++-
 gdb/record-btrace.c                           |  16 +-
 gdb/remote.c                                  |   4 +-
 gdb/target.c                                  |   2 +
 gdb/testsuite/gdb.base/break.exp              |   2 +-
 gdb/testsuite/gdb.base/default.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/base.exp              |   4 +
 gdb/testsuite/gdb.multi/info-threads.exp      |   2 +-
 gdb/testsuite/gdb.multi/per-inferior-tids.c   |  40 +++
 gdb/testsuite/gdb.multi/per-inferior-tids.exp | 153 ++++++++++
 gdb/testsuite/gdb.python/py-infthread.exp     |   6 +-
 gdb/testsuite/gdb.threads/thread-specific.exp |   5 +
 gdb/thread.c                                  | 415 +++++++++++++++++++-------
 gdb/varobj.c                                  |   9 +-
 42 files changed, 971 insertions(+), 326 deletions(-)
 create mode 100644 gdb/testsuite/gdb.multi/per-inferior-tids.c
 create mode 100644 gdb/testsuite/gdb.multi/per-inferior-tids.exp

Comments

Eli Zaretskii Dec. 15, 2015, 7:39 p.m. UTC | #1
> From: Pedro Alves <palves@redhat.com>
> Date: Tue, 15 Dec 2015 19:05:16 +0000
> 
> +See @xref{Convenience Vars,, Convenience Variables}, for general

Either "See @ref" or just "@xref" (which will produce "See").

Other than that, the documentation parts are OK, with one comment: it
seems confusing to talk about "per-inferior ID" when evidently you
refer to the 2.3 form of the thread ID.  Can we find some other term
for this ID?

Thanks.
Joel Brobecker Dec. 18, 2015, 5:53 p.m. UTC | #2
> However, before I dig deeper, I thought I'd post this for feedback.

FWIW, I think that this is a fairly nice way of addressing the problem!

The only question I have is that I'm a little unclear as to what
it will look like with GDB/MI. From what I can tell, there is no
real change at all, meaning that the "thread-id" is the thread's
global ID. But perhaps it wouuld be nice to add an extra field
giving the thread's ID in string form.  Newer IDEs knowing about
this new feature would then have an easy way to present the list
of threads using the same representation as the one we see with
the CLI interface. Just a thought...

Another way would be to provide the per-inferior-thread-id in numeric
form, which should be sufficient, since I think the inferior-id is
already provided.  It has a cleaner feel to it, but on the other hand,
it forces the IDEs to rebuild the thread ID by hand - which is not so
simple, since we have this exception where if there is one inferior
whose ID is 1, we don't use the composite thread ID.

That being said, maybe the IDEs don't even (need to) display
that ID... So we could also leave that question open for debate
when the need actually materializes...
Pedro Alves Dec. 18, 2015, 6:47 p.m. UTC | #3
On 12/18/2015 05:53 PM, Joel Brobecker wrote:
>> However, before I dig deeper, I thought I'd post this for feedback.
> 
> FWIW, I think that this is a fairly nice way of addressing the problem!
> 
> The only question I have is that I'm a little unclear as to what
> it will look like with GDB/MI. From what I can tell, there is no
> real change at all, meaning that the "thread-id" is the thread's
> global ID. 

Exactly, no change at all.

> But perhaps it wouuld be nice to add an extra field
> giving the thread's ID in string form.  Newer IDEs knowing about
> this new feature would then have an easy way to present the list
> of threads using the same representation as the one we see with
> the CLI interface. Just a thought...

Yeah.

> 
> Another way would be to provide the per-inferior-thread-id in numeric
> form, which should be sufficient, since I think the inferior-id is
> already provided.  It has a cleaner feel to it, but on the other hand,
> it forces the IDEs to rebuild the thread ID by hand - which is not so
> simple, since we have this exception where if there is one inferior
> whose ID is 1, we don't use the composite thread ID.

Indeed, hadn't thought of that point.

In an earlier revision of the patch I followed the numeric approach.
I had a couple new fields to -thread-info output, like:

 - ^done,threads=[{id="6", ...} ...
 + ^done,threads=[{thread-group="i2", per-tg-id="3",id="6", ...} ...

and:

 - =thread-created,id=3,group-id="i2"
 + =thread-created,id=3,group-id="i2",per-tg-id="3"

But after chatting with Marc Khouzam (Eclipse CDT) about the whole
change's impact, we came to the conclusion that we can add this later
when we find a needed.  So I left that out of the patch for now.

Maybe we should need the new field in the =thread-created event,
not sure.  Or we go through everywhere and make sure we're consistent.
I'm just dodging thinking about all that for now.  :-)

> 
> That being said, maybe the IDEs don't even (need to) display
> that ID... So we could also leave that question open for debate
> when the need actually materializes...
> 

That's my hope. :-)

Thanks,
Pedro Alves
diff mbox

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 9ca7f49..1d22da6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,45 @@ 
 
 *** Changes since GDB 7.10
 
+* Per-inferior thread IDs
+
+  Thread numbers are now per inferior instead of global.  If you're
+  debugging multiple inferiors, GDB displays thread IDs using an
+  expanded 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)
+
+  Commands that accept thread IDs now accept the expanded form as
+  well:
+
+     (gdb) thread 2.1
+     [Switching to thread 2.1 (Thread 0x7ffff7fc2740 (LWP 8157))] (running)
+     (gdb)
+
+  As consequence, thread IDs as visible in the $_thread convenience
+  variable 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 IDs in
+  previous releases.
+
+  For backwards compatibility, MI's thread IDs always refer to the
+  global ID.
+
+* You can use "info threads -gid" to display the global thread ID of
+  all threads.
+
+* The new convenience variable $_gthread holds the global number of
+  the current thread.
+
+* The new convenience variable $_inferior holds the number of the
+  current inferior.
+
 * Record btrace now supports non-stop mode.
 
 * Support for tracepoints on aarch64-linux was added in GDBserver.
@@ -141,6 +180,14 @@  show remote exec-event-feature-packet
      format.  It outputs data in hexadecimal format with zero-padding on the
      left.
 
+* Python Scripting
+
+  ** gdb.InferiorThread objects have a new attribute "global_num",
+     which refers to the thread's global thread ID.  The existing
+     "num" attribute now refers to the thread's per-inferior ID.
+  ** gdb.InferiorThread objects have a new attribute "inferior", which
+     is the Inferior object the thread belongs to.
+
 *** Changes in GDB 7.10
 
 * Support for process record-replay and reverse debugging on aarch64*-linux*
diff --git a/gdb/ada-tasks.c b/gdb/ada-tasks.c
index e2194dd..5743a88 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 f105042..1453d9c 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -3144,7 +3144,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,13 +3244,13 @@  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_id && user_breakpoint_p (b))
 	{
 	  b->disposition = disp_del_at_next_stop;
 
 	  printf_filtered (_("\
-Thread-specific breakpoint %d deleted - thread %d no longer in the thread list.\n"),
-			  b->number, tp->num);
+Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\n"),
+			   b->number, print_thread_id (tp));
 
 	  /* Hide it from the user.  */
 	  b->number = 0;
@@ -5447,7 +5447,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)))
 
     {
@@ -6516,7 +6516,14 @@  print_one_breakpoint_location (struct breakpoint *b,
     {
       /* FIXME should make an annotation for this.  */
       ui_out_text (uiout, "\tstop only in thread ");
-      ui_out_field_int (uiout, "thread", b->thread);
+      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");
     }
   
@@ -7572,7 +7579,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_id;
 
   /* To avoid having to rescan all objfile symbols at every step,
      we maintain a list of continually-inserted but always disabled
@@ -7641,7 +7648,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.  */
 
@@ -7671,7 +7678,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_id)
       {
 	struct breakpoint *dummy_b = b->related_breakpoint;
 
@@ -8891,7 +8898,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);
 
@@ -9565,14 +9572,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.
@@ -9621,14 +9620,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_id;
 	  tok = tmptok;
 	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
@@ -11133,25 +11132,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);
 
-	      /* Check if the thread actually exists.  */
-	      if (!valid_thread_id (thread))
-		invalid_thread_id_error (thread);
+	      thread = thr->global_id;
 	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
@@ -11692,7 +11689,7 @@  until_break_command (char *arg, int from_tty, int anywhere)
   resolve_sal_pc (&sal);
 
   tp = inferior_thread ();
-  thread = tp->num;
+  thread = tp->global_id;
 
   old_chain = make_cleanup (null_cleanup, NULL);
 
@@ -11742,7 +11739,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_id,
+			    location_breakpoint, caller_breakpoint);
   tp->thread_fsm = &sm->thread_fsm;
 
   discard_cleanups (old_chain);
@@ -13364,7 +13362,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;
@@ -14545,7 +14543,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
@@ -15056,7 +15054,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_id, gdbarch);
     }
 
   sal = find_pc_line (pc, 0);
diff --git a/gdb/btrace.c b/gdb/btrace.c
index 35431cb..01bc427 100644
--- a/gdb/btrace.c
+++ b/gdb/btrace.c
@@ -1043,7 +1043,8 @@  btrace_enable (struct thread_info *tp, const struct btrace_config *conf)
   if (!target_supports_btrace (conf->format))
     error (_("Target does not support branch tracing."));
 
-  DEBUG ("enable thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
+  DEBUG ("enable thread %s (%s)", print_thread_id (tp),
+	 target_pid_to_str (tp->ptid));
 
   tp->btrace.target = target_enable_btrace (tp->ptid, conf);
 
@@ -1075,7 +1076,8 @@  btrace_disable (struct thread_info *tp)
   if (btp->target == NULL)
     return;
 
-  DEBUG ("disable thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
+  DEBUG ("disable thread %s (%s)", print_thread_id (tp),
+	 target_pid_to_str (tp->ptid));
 
   target_disable_btrace (btp->target);
   btp->target = NULL;
@@ -1094,7 +1096,8 @@  btrace_teardown (struct thread_info *tp)
   if (btp->target == NULL)
     return;
 
-  DEBUG ("teardown thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
+  DEBUG ("teardown thread %s (%s)", print_thread_id (tp),
+	 target_pid_to_str (tp->ptid));
 
   target_teardown_btrace (btp->target);
   btp->target = NULL;
@@ -1268,7 +1271,8 @@  btrace_fetch (struct thread_info *tp)
   struct cleanup *cleanup;
   int errcode;
 
-  DEBUG ("fetch thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
+  DEBUG ("fetch thread %s (%s)", print_thread_id (tp),
+	 target_pid_to_str (tp->ptid));
 
   btinfo = &tp->btrace;
   tinfo = btinfo->target;
@@ -1340,7 +1344,8 @@  btrace_clear (struct thread_info *tp)
   struct btrace_thread_info *btinfo;
   struct btrace_function *it, *trash;
 
-  DEBUG ("clear thread %d (%s)", tp->num, target_pid_to_str (tp->ptid));
+  DEBUG ("clear thread %s (%s)", print_thread_id (tp),
+	 target_pid_to_str (tp->ptid));
 
   /* Make sure btrace frames that may hold a pointer into the branch
      trace data are destroyed.  */
diff --git a/gdb/cli/cli-utils.c b/gdb/cli/cli-utils.c
index 5c6338b..828c895 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 ad46581..9ee8f09 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/common/print-utils.c b/gdb/common/print-utils.c
index 7eba07d..a8914b1 100644
--- a/gdb/common/print-utils.c
+++ b/gdb/common/print-utils.c
@@ -22,14 +22,13 @@ 
 /* Temporary storage using circular buffer.  */
 
 #define NUMCELLS 16
-#define CELLSIZE 50
 
 /* Return the next entry in the circular buffer.  */
 
-static char *
-get_cell (void)
+char *
+get_print_cell (void)
 {
-  static char buf[NUMCELLS][CELLSIZE];
+  static char buf[NUMCELLS][PRINT_CELL_SIZE];
   static int cell = 0;
 
   if (++cell >= NUMCELLS)
@@ -43,7 +42,7 @@  decimal2str (char *sign, ULONGEST addr, int width)
   /* Steal code from valprint.c:print_decimal().  Should this worry
      about the real size of addr as the above does?  */
   unsigned long temp[3];
-  char *str = get_cell ();
+  char *str = get_print_cell ();
   int i = 0;
 
   do
@@ -62,14 +61,14 @@  decimal2str (char *sign, ULONGEST addr, int width)
   switch (i)
     {
     case 1:
-      xsnprintf (str, CELLSIZE, "%s%0*lu", sign, width, temp[0]);
+      xsnprintf (str, PRINT_CELL_SIZE, "%s%0*lu", sign, width, temp[0]);
       break;
     case 2:
-      xsnprintf (str, CELLSIZE, "%s%0*lu%09lu", sign, width,
+      xsnprintf (str, PRINT_CELL_SIZE, "%s%0*lu%09lu", sign, width,
 		 temp[1], temp[0]);
       break;
     case 3:
-      xsnprintf (str, CELLSIZE, "%s%0*lu%09lu%09lu", sign, width,
+      xsnprintf (str, PRINT_CELL_SIZE, "%s%0*lu%09lu%09lu", sign, width,
 		 temp[2], temp[1], temp[0]);
       break;
     default:
@@ -84,7 +83,7 @@  static char *
 octal2str (ULONGEST addr, int width)
 {
   unsigned long temp[3];
-  char *str = get_cell ();
+  char *str = get_print_cell ();
   int i = 0;
 
   do
@@ -104,15 +103,15 @@  octal2str (ULONGEST addr, int width)
     {
     case 1:
       if (temp[0] == 0)
-	xsnprintf (str, CELLSIZE, "%*o", width, 0);
+	xsnprintf (str, PRINT_CELL_SIZE, "%*o", width, 0);
       else
-	xsnprintf (str, CELLSIZE, "0%0*lo", width, temp[0]);
+	xsnprintf (str, PRINT_CELL_SIZE, "0%0*lo", width, temp[0]);
       break;
     case 2:
-      xsnprintf (str, CELLSIZE, "0%0*lo%010lo", width, temp[1], temp[0]);
+      xsnprintf (str, PRINT_CELL_SIZE, "0%0*lo%010lo", width, temp[1], temp[0]);
       break;
     case 3:
-      xsnprintf (str, CELLSIZE, "0%0*lo%010lo%010lo", width,
+      xsnprintf (str, PRINT_CELL_SIZE, "0%0*lo%010lo%010lo", width,
 		 temp[2], temp[1], temp[0]);
       break;
     default:
@@ -155,18 +154,18 @@  phex (ULONGEST l, int sizeof_l)
   switch (sizeof_l)
     {
     case 8:
-      str = get_cell ();
-      xsnprintf (str, CELLSIZE, "%08lx%08lx",
+      str = get_print_cell ();
+      xsnprintf (str, PRINT_CELL_SIZE, "%08lx%08lx",
 		 (unsigned long) (l >> thirty_two),
 		 (unsigned long) (l & 0xffffffff));
       break;
     case 4:
-      str = get_cell ();
-      xsnprintf (str, CELLSIZE, "%08lx", (unsigned long) l);
+      str = get_print_cell ();
+      xsnprintf (str, PRINT_CELL_SIZE, "%08lx", (unsigned long) l);
       break;
     case 2:
-      str = get_cell ();
-      xsnprintf (str, CELLSIZE, "%04x", (unsigned short) (l & 0xffff));
+      str = get_print_cell ();
+      xsnprintf (str, PRINT_CELL_SIZE, "%04x", (unsigned short) (l & 0xffff));
       break;
     default:
       str = phex (l, sizeof (l));
@@ -189,22 +188,22 @@  phex_nz (ULONGEST l, int sizeof_l)
       {
 	unsigned long high = (unsigned long) (l >> thirty_two);
 
-	str = get_cell ();
+	str = get_print_cell ();
 	if (high == 0)
-	  xsnprintf (str, CELLSIZE, "%lx",
+	  xsnprintf (str, PRINT_CELL_SIZE, "%lx",
 		     (unsigned long) (l & 0xffffffff));
 	else
-	  xsnprintf (str, CELLSIZE, "%lx%08lx", high,
+	  xsnprintf (str, PRINT_CELL_SIZE, "%lx%08lx", high,
 		     (unsigned long) (l & 0xffffffff));
 	break;
       }
     case 4:
-      str = get_cell ();
-      xsnprintf (str, CELLSIZE, "%lx", (unsigned long) l);
+      str = get_print_cell ();
+      xsnprintf (str, PRINT_CELL_SIZE, "%lx", (unsigned long) l);
       break;
     case 2:
-      str = get_cell ();
-      xsnprintf (str, CELLSIZE, "%x", (unsigned short) (l & 0xffff));
+      str = get_print_cell ();
+      xsnprintf (str, PRINT_CELL_SIZE, "%x", (unsigned short) (l & 0xffff));
       break;
     default:
       str = phex_nz (l, sizeof (l));
@@ -219,9 +218,9 @@  phex_nz (ULONGEST l, int sizeof_l)
 char *
 hex_string (LONGEST num)
 {
-  char *result = get_cell ();
+  char *result = get_print_cell ();
 
-  xsnprintf (result, CELLSIZE, "0x%s", phex_nz (num, sizeof (num)));
+  xsnprintf (result, PRINT_CELL_SIZE, "0x%s", phex_nz (num, sizeof (num)));
   return result;
 }
 
@@ -230,14 +229,14 @@  hex_string (LONGEST num)
 char *
 hex_string_custom (LONGEST num, int width)
 {
-  char *result = get_cell ();
-  char *result_end = result + CELLSIZE - 1;
+  char *result = get_print_cell ();
+  char *result_end = result + PRINT_CELL_SIZE - 1;
   const char *hex = phex_nz (num, sizeof (num));
   int hex_len = strlen (hex);
 
   if (hex_len > width)
     width = hex_len;
-  if (width + 2 >= CELLSIZE)
+  if (width + 2 >= PRINT_CELL_SIZE)
     internal_error (__FILE__, __LINE__, _("\
 hex_string_custom: insufficient space to store result"));
 
@@ -294,7 +293,7 @@  int_string (LONGEST val, int radix, int is_signed, int width,
 const char *
 core_addr_to_string (const CORE_ADDR addr)
 {
-  char *str = get_cell ();
+  char *str = get_print_cell ();
 
   strcpy (str, "0x");
   strcat (str, phex (addr, sizeof (addr)));
@@ -306,7 +305,7 @@  core_addr_to_string (const CORE_ADDR addr)
 const char *
 core_addr_to_string_nz (const CORE_ADDR addr)
 {
-  char *str = get_cell ();
+  char *str = get_print_cell ();
 
   strcpy (str, "0x");
   strcat (str, phex_nz (addr, sizeof (addr)));
@@ -318,8 +317,9 @@  core_addr_to_string_nz (const CORE_ADDR addr)
 const char *
 host_address_to_string_1 (const void *addr)
 {
-  char *str = get_cell ();
+  char *str = get_print_cell ();
 
-  xsnprintf (str, CELLSIZE, "0x%s", phex_nz ((uintptr_t) addr, sizeof (addr)));
+  xsnprintf (str, PRINT_CELL_SIZE, "0x%s",
+	     phex_nz ((uintptr_t) addr, sizeof (addr)));
   return str;
 }
diff --git a/gdb/common/print-utils.h b/gdb/common/print-utils.h
index 49bc09a..f045d71 100644
--- a/gdb/common/print-utils.h
+++ b/gdb/common/print-utils.h
@@ -71,4 +71,8 @@  extern const char *host_address_to_string_1 (const void *addr);
 #define host_address_to_string(ADDR) \
   host_address_to_string_1 ((const void *) (ADDR))
 
+extern char *get_print_cell (void);
+
+#define PRINT_CELL_SIZE 50
+
 #endif /* COMMON_CELLS_H */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index bb68e21..5375047 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -2658,6 +2658,12 @@  Make inferior number @var{infno} the current inferior.  The argument
 in the first field of the @samp{info inferiors} display.
 @end table
 
+@vindex $_inferior@r{, convenience variable}
+The debugger convenience variable @samp{$_inferior} contains the
+number of the current inferior.  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.
 
 You can get multiple executables into a debugging session via the
 @code{add-inferior} and @w{@code{clone-inferior}} commands.  On some
@@ -2880,10 +2886,31 @@  further qualifier.
 @c         multithread systems permit starting a program with multiple
 @c         threads ab initio?
 
-@cindex thread number
+@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 ID
+is unique between all threads of an inferior, but not unique between
+threads of different inferiors.
+
+You can refer to a given thread in an inferior using the
+@var{inferior-num}.@var{thread-num} syntax, 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}, 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.
+
+@cindex global thread number
+@cindex global thread identifier (GDB)
+In addition to a @emph{per-inferior} ID, each thread is also assigned
+a unique @emph{global} ID.  Unlike the former, 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
@@ -2899,7 +2926,11 @@  means to print information only about the specified thread or threads.
 
 @enumerate
 @item
-the thread number assigned by @value{GDBN}
+the per-inferior thread number assigned by @value{GDBN}
+
+@item
+the global thread number assigned by @value{GDBN}, if the @samp{-gid}
+option was specified.
 
 @item
 the target system's thread identifier (@var{systag})
@@ -2930,6 +2961,21 @@  For example,
     at threadtest.c:68
 @end smallexample
 
+If you're debugging multiple inferiors, @value{GDBN} displays thread
+IDs using an expanded @var{inferior-num}.@var{thread-num} format.
+
+If you specify the @samp{-gid} option, @value{GDBN} displays a column
+indicating each thread's global thread ID:
+
+@smallexample
+(@value{GDBP}) info threads
+  Id   GId  Target Id             Frame
+  1.1  1    process 35 thread 13  main (argc=1, argv=0x7ffffff8)
+  1.2  3    process 35 thread 23  0x34e5 in sigpause ()
+  1.3  4    process 35 thread 27  0x34e5 in sigpause ()
+* 2.1  2    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:
 
@@ -2944,10 +2990,10 @@  Display info on Solaris user threads.
 @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:
+argument @var{threadno} is the internal per-inferior @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:
 
 @smallexample
 (@value{GDBP}) thread 2
@@ -2962,11 +3008,19 @@  As with the @samp{[New @dots{}]} message, the form of the text after
 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.
+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.  See @xref{Convenience Vars,, Convenience Variables},
+for general information on convenience variables.
+
+@vindex $_gthread@r{, convenience variable}
+The debugger convenience variable @samp{$_gthread} contains the global
+number of the current thread, independent of the thread's containing
+inferior.  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
@@ -2975,9 +3029,10 @@  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
+shown in the first field of the @samp{info threads} display, with or
+without a inferior qualifier (e.g., @samp{2.1} or @samp{1}); or it
+could be a range of thread numbers, as in @code{2-4}.  To apply a
+command to all threads in descending order, type @kbd{thread apply all
 @var{command}}.  To apply a command to all threads in ascending order,
 type @kbd{thread apply all -ascending @var{command}}.
 
@@ -25357,8 +25412,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
@@ -25911,15 +25966,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
@@ -25973,8 +26029,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
@@ -26010,7 +26067,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}"
@@ -26271,7 +26328,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
@@ -26744,7 +26801,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
@@ -26834,7 +26892,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
@@ -27484,10 +27543,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 @emph{global} thread ID.  When
+printing information about all threads, also reports the global ID of
+the current thread.
 
 @subsubheading @value{GDBN} Command
 
@@ -27504,7 +27564,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.
@@ -27570,8 +27630,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.
@@ -27600,8 +27661,9 @@  current-thread-id="1",number-of-threads="3"
  -thread-select @var{threadnum}
 @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 num @var{threadnum} 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.
@@ -27670,7 +27732,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
@@ -28886,7 +28949,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
@@ -29067,8 +29130,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 a0147c1..1e6abbc 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 a2df254..f9fb665 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -2995,7 +2995,11 @@  user-specified thread name.
 @end defvar
 
 @defvar InferiorThread.num
-ID of the thread, as assigned by GDB.
+The per-inferior ID of the thread, as assigned by GDB.
+@end defvar
+
+@defvar InferiorThread.global_num
+The global ID of the thread, as assigned by GDB.
 @end defvar
 
 @defvar InferiorThread.ptid
@@ -4638,9 +4642,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 2aacf5b..374c395 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 138d316..c978025 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 eb7f8b8..50dfffc 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -187,7 +187,33 @@  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 ID's -- one global (GId), and one
+     per-inferior (Id):
+
+      (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 id.  It's exposed to MI,
+     Python/Scheme, visible with "info threads -gid", and is also what
+     the $_thread convenience variable is bound to.  */
+  int global_id;
+
+  /* The GDB thread Id unique in the inferior the thread belongs to.
+     Each inferior has its own number space for this ID.  */
+  int per_inf_id;
+
+  /* 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,27 +379,38 @@  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 integer thread id (GDB's homegrown global 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 integer thread id (GDB's homegrown id, not the system's).  */
-extern int pid_to_thread_id (ptid_t ptid);
+extern int ptid_to_global_thread_id (ptid_t ptid);
+
+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 thread id (GDB's homegrown global
+   id, not the system's).  */
+extern int valid_global_thread_id (int thread);
+
+/* 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);
 
 /* 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);
+struct thread_info *find_thread_global_id (int num);
 
 /* Finds the first thread of the inferior given by PID.  If PID is -1,
    returns the first thread in the list.  */
@@ -402,6 +439,9 @@  extern struct thread_info *iterate_over_threads (thread_callback_func, void *);
   for (T = thread_list; T; T = T->next) \
     if ((T)->state != THREAD_EXITED)
 
+#define ALL_THREADS(T)				\
+  for (T = thread_list; T; T = T->next) \
+
 /* Traverse all threads, including those that have THREAD_EXITED
    state.  Allows deleting the currently iterated thread.  */
 #define ALL_THREADS_SAFE(T, TMP)	\
@@ -500,7 +540,15 @@  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 is not NULL, it's a list of GDB ids 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_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 b599e23..2922f48 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 ea689f5..2ed70b5 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_id;
 
   /* 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)
     {
@@ -1316,8 +1316,8 @@  signal_command (char *signum_exp, int from_tty)
 	    {
 	      if (!must_confirm)
 		printf_unfiltered (_("Note:\n"));
-	      printf_unfiltered (_("  Thread %d previously stopped with signal %s, %s.\n"),
-				 tp->num,
+	      printf_unfiltered (_("  Thread %s previously stopped with signal %s, %s.\n"),
+				 print_thread_id (tp),
 				 gdb_signal_to_name (tp->suspend.stop_signal),
 				 gdb_signal_to_string (tp->suspend.stop_signal));
 	      must_confirm = 1;
@@ -1325,10 +1325,10 @@  signal_command (char *signum_exp, int from_tty)
 	}
 
       if (must_confirm
-	  && !query (_("Continuing thread %d (the current thread) with specified signal will\n"
+	  && !query (_("Continuing thread %s (the current thread) with specified signal will\n"
 		       "still deliver the signals noted above to their respective threads.\n"
 		       "Continue anyway? "),
-		     inferior_thread ()->num))
+		     print_thread_id (inferior_thread ())))
 	error (_("Not confirmed."));
     }
 
@@ -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_id;
   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_id);
   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_id);
 
   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_id < lowest->per_inf_id)
 		    lowest = thread;
 		}
 	    }
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 157e236..b632a53 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -531,6 +531,15 @@  inferior_pid_to_str (int pid)
     return _("<null>");
 }
 
+static int
+should_print_inferior (const char *requested_inferiors, struct inferior *inf)
+{
+  if (requested_inferiors != NULL && *requested_inferiors != '\0')
+    return number_is_in_list (requested_inferiors, inf->num);
+  else
+    return 1;
+}
+
 /* Prints the list of inferiors and their details on UIOUT.  This is a
    version of 'info_inferior_command' suitable for use from MI.
 
@@ -548,7 +557,7 @@  print_inferior (struct ui_out *uiout, char *requested_inferiors)
   /* Compute number of inferiors we will print.  */
   for (inf = inferior_list; inf; inf = inf->next)
     {
-      if (!number_is_in_list (requested_inferiors, inf->num))
+      if (!should_print_inferior (requested_inferiors, inf))
 	continue;
 
       ++inf_count;
@@ -572,7 +581,7 @@  print_inferior (struct ui_out *uiout, char *requested_inferiors)
     {
       struct cleanup *chain2;
 
-      if (!number_is_in_list (requested_inferiors, inf->num))
+      if (!should_print_inferior (requested_inferiors, inf))
 	continue;
 
       chain2 = make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
@@ -729,8 +738,8 @@  inferior_command (char *args, int from_tty)
 	  switch_to_thread (tp->ptid);
 	}
 
-      printf_filtered (_("[Switching to thread %d (%s)] "),
-		       pid_to_thread_id (inferior_ptid),
+      printf_filtered (_("[Switching to thread %s (%s)] "),
+		       print_thread_id (inferior_thread ()),
 		       target_pid_to_str (inferior_ptid));
     }
   else
@@ -989,6 +998,26 @@  show_print_inferior_events (struct ui_file *file, int from_tty,
   fprintf_filtered (file, _("Printing of inferior events is %s.\n"), value);
 }
 
+/* Return a new value for the selected inferior's id.  */
+
+static struct value *
+inferior_id_make_value (struct gdbarch *gdbarch, struct internalvar *var,
+			void *ignore)
+{
+  struct inferior *inf = current_inferior ();
+
+  return value_from_longest (builtin_type (gdbarch)->builtin_int, inf->num);
+}
+
+/* Implementation of `$_inferior' variable.  */
+
+static const struct internalvar_funcs inferior_funcs =
+{
+  inferior_id_make_value,
+  NULL,
+  NULL
+};
+
 
 
 void
@@ -1052,4 +1081,5 @@  Show printing of inferior events (e.g., inferior start and exit)."), NULL,
          show_print_inferior_events,
          &setprintlist, &showprintlist);
 
+  create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
 }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index d3cf615..7e13910 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -304,6 +304,8 @@  struct inferior
   /* True if the PID was actually faked by GDB.  */
   int fake_pid_p;
 
+  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 f9bb411..a45b87f 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_id;
 	  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_id;
   inferior_thread ()->control.exception_resume_breakpoint = bp;
 }
 
@@ -7905,7 +7905,7 @@  print_signal_received_reason (struct ui_out *uiout, enum gdb_signal siggnal)
       ui_out_text (uiout, "\n[");
       ui_out_field_string (uiout, "thread-name",
 			   target_pid_to_str (t->ptid));
-      ui_out_field_fmt (uiout, "thread-id", "] #%d", t->num);
+      ui_out_field_fmt (uiout, "thread-id", "] #%d", t->global_id);
       ui_out_text (uiout, " stopped");
     }
   else
diff --git a/gdb/mi/mi-cmd-var.c b/gdb/mi/mi-cmd-var.c
index 643fd7a..d532797 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 b349df2..4eb16fa 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_id, 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_id, 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_id);
       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_id);
 	  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_id);
 
   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_id);
     }
 
   if (!running_result_record_printed && mi_proceeded)
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 2b25a9c..89aaf98 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_id != 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_id);
 	      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 9c0b0e4..82cbc27 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 45a7d87..e91c5d2 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 e5db354..381235f 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,19 @@  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_id);
+}
+
+/* Getter for InferiorThread.global_num.  */
+
+static PyObject *
+thpy_get_global_num (PyObject *self, void *closure)
+{
+  thread_object *thread_obj = (thread_object *) self;
+
+  THPY_REQUIRE_VALID (thread_obj);
+
+  return PyLong_FromLong (thread_obj->thread->global_id);
 }
 
 /* Getter for InferiorThread.ptid  -> (pid, lwp, tid).
@@ -140,6 +154,18 @@  thpy_get_ptid (PyObject *self, void *closure)
   return gdbpy_create_ptid_object (thread_obj->thread->ptid);
 }
 
+/* Getter for InferiorThread.inferior -> Inferior.  */
+
+static PyObject *
+thpy_get_inferior (PyObject *self, void *ignore)
+{
+  thread_object *thread_obj = (thread_object *) self;
+
+  THPY_REQUIRE_VALID (thread_obj);
+
+  return inferior_to_inferior_object (thread_obj->thread->inf);
+}
+
 /* Implementation of InferiorThread.switch ().
    Makes this the GDB selected thread.  */
 
@@ -282,9 +308,14 @@  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 ID of the thread, as assigned by GDB.", NULL },
+  { "global_num", thpy_get_global_num, NULL,
+    "Global ID 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,
+    "The Inferior object this thread belongs to.", NULL },
 
   { NULL }
 };
diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
index 661ce53..b3a7d6a 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_id))
       {
 	btrace_enable (tp, &record_btrace_conf);
 
@@ -438,8 +438,8 @@  record_btrace_info (struct target_ops *self)
     }
 
   printf_unfiltered (_("Recorded %u instructions in %u functions (%u gaps) "
-		       "for thread %d (%s).\n"), insns, calls, gaps,
-		     tp->num, target_pid_to_str (tp->ptid));
+		       "for thread %s (%s).\n"), insns, calls, gaps,
+		     print_thread_id (tp), target_pid_to_str (tp->ptid));
 
   if (btrace_is_replaying (tp))
     printf_unfiltered (_("Replay in progress.  At instruction %u.\n"),
@@ -1864,7 +1864,7 @@  record_btrace_resume_thread (struct thread_info *tp,
 {
   struct btrace_thread_info *btinfo;
 
-  DEBUG ("resuming thread %d (%s): %x (%s)", tp->num,
+  DEBUG ("resuming thread %s (%s): %x (%s)", print_thread_id (tp),
 	 target_pid_to_str (tp->ptid), flag, btrace_thread_flag_to_str (flag));
 
   btinfo = &tp->btrace;
@@ -2131,7 +2131,8 @@  record_btrace_cancel_resume (struct thread_info *tp)
   if (flags == 0)
     return;
 
-  DEBUG ("cancel resume thread %d (%s): %x (%s)", tp->num,
+  DEBUG ("cancel resume thread %s (%s): %x (%s)",
+	 print_thread_id (tp),
 	 target_pid_to_str (tp->ptid), flags,
 	 btrace_thread_flag_to_str (flags));
 
@@ -2354,7 +2355,7 @@  record_btrace_step_thread (struct thread_info *tp)
   flags = btinfo->flags & (BTHR_MOVE | BTHR_STOP);
   btinfo->flags &= ~(BTHR_MOVE | BTHR_STOP);
 
-  DEBUG ("stepping thread %d (%s): %x (%s)", tp->num,
+  DEBUG ("stepping thread %s (%s): %x (%s)", print_thread_id (tp),
 	 target_pid_to_str (tp->ptid), flags,
 	 btrace_thread_flag_to_str (flags));
 
@@ -2563,7 +2564,8 @@  record_btrace_wait (struct target_ops *ops, ptid_t ptid,
   /* We moved the replay position but did not update registers.  */
   registers_changed_ptid (eventing->ptid);
 
-  DEBUG ("wait ended by thread %d (%s): %s", eventing->num,
+  DEBUG ("wait ended by thread %s (%s): %s",
+	 print_thread_id (eventing),
 	 target_pid_to_str (eventing->ptid),
 	 target_waitstatus_to_string (status));
 
diff --git a/gdb/remote.c b/gdb/remote.c
index 52c5df8..1bf58ef 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -3880,7 +3880,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_id < lowest_stopped->per_inf_id)
 	lowest_stopped = thread;
 
       if (non_stop)
diff --git a/gdb/target.c b/gdb/target.c
index e5e8172..ef5f3cd 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 f879bc8..82ce744 100644
--- a/gdb/testsuite/gdb.base/break.exp
+++ b/gdb/testsuite/gdb.base/break.exp
@@ -590,7 +590,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.*" \
+    "Bad thread spec.*" \
     "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/default.exp b/gdb/testsuite/gdb.base/default.exp
index 4395c98..ff5c062 100644
--- a/gdb/testsuite/gdb.base/default.exp
+++ b/gdb/testsuite/gdb.base/default.exp
@@ -587,6 +587,8 @@  set show_conv_list \
 	{$_sdata = void} \
 	{$_siginfo = void} \
 	{$_thread = 0} \
+	{$_gthread = 0} \
+	{$_inferior = 1} \
 	{$_exception = <error: No frame selected>} \
 	{$_probe_argc = <error: No frame selected>} \
 	{$_probe_arg0 = <error: No frame selected>} \
diff --git a/gdb/testsuite/gdb.base/hbreak2.exp b/gdb/testsuite/gdb.base/hbreak2.exp
index 0815a96..1e29e00 100644
--- a/gdb/testsuite/gdb.base/hbreak2.exp
+++ b/gdb/testsuite/gdb.base/hbreak2.exp
@@ -347,7 +347,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.*" \
+    "Bad thread spec.*" \
     "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 c363be4..0cac4ec 100644
--- a/gdb/testsuite/gdb.base/sepdebug.exp
+++ b/gdb/testsuite/gdb.base/sepdebug.exp
@@ -389,7 +389,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.*" \
+    "Bad thread spec.*" \
     "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 6d767c2..5d1af33 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" "Bad thread spec.*" "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 fcb745f..942b646 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" "Bad thread spec '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" "Bad thread spec 'thread'"
+gdb_test "break thread task" "Bad thread spec 'task'"
+gdb_test "break thread if" "Bad thread spec '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/base.exp b/gdb/testsuite/gdb.multi/base.exp
index 0c74d98..4cabef0 100644
--- a/gdb/testsuite/gdb.multi/base.exp
+++ b/gdb/testsuite/gdb.multi/base.exp
@@ -44,12 +44,16 @@  if { [build_executable ${testfile}.exp ${exec3} "${srcfile3}" {debug}] == -1 } {
 
 clean_restart ${exec1}
 
+gdb_test {print $_inferior} " = 1"
+
 # Add an empty inferior, switch to it, and load a main executable into
 # it.
 gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
 gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
 gdb_test "file ${binfile2}" ".*" "load ${exec2} file in inferior 2"
 
+gdb_test {print $_inferior} " = 2" "print \$_inferior after switching"
+
 # Add a new inferior and load a main executable into it in one
 # command.
 gdb_test "add-inferior -exec ${binfile3}" \
diff --git a/gdb/testsuite/gdb.multi/info-threads.exp b/gdb/testsuite/gdb.multi/info-threads.exp
index c00b8d7..b949e17 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/per-inferior-tids.c b/gdb/testsuite/gdb.multi/per-inferior-tids.c
new file mode 100644
index 0000000..9e77031
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/per-inferior-tids.c
@@ -0,0 +1,40 @@ 
+/* 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>
+
+void *
+thread_function (void *arg)
+{
+  while (1)
+    sleep (1);
+}
+
+int
+main (void)
+{
+  pthread_t child_thread;
+  int i;
+
+  alarm (300);
+
+  pthread_create (&child_thread, NULL, thread_function, NULL);
+  pthread_join (child_thread, NULL);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/per-inferior-tids.exp b/gdb/testsuite/gdb.multi/per-inferior-tids.exp
new file mode 100644
index 0000000..e892758
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/per-inferior-tids.exp
@@ -0,0 +1,153 @@ 
+# 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/>.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} {pthreads debug}] } {
+    return -1
+}
+
+clean_restart ${testfile}
+
+if { ![runto_main] } then {
+    return -1
+}
+
+# "info threads" while there's only inferior 1 should show
+# single-number thread IDs.
+with_test_prefix "single inferior" {
+    gdb_test "info threads" " 1 .* main .* at .*$srcfile:.*"
+
+    gdb_test "thread" "Current thread is 1 .*"
+}
+
+# "info threads" while there are multiple inferior should show
+# expanded 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.
+    gdb_test "info threads" " 1\.1 .* main .* at .*$srcfile:.*"
+
+    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.
+    gdb_test "info threads" \
+	[multi_line \
+	     "  1\.1 .* main .* at .*$srcfile:.*" \
+	     "\\* 2\.1 .* main .* at .*$srcfile:.*"] \
+	"info threads show inferior numbers"
+
+    gdb_test "thread" "Current thread is 2\.1 .*" \
+	"switch to thread using extended thread ID"
+
+    gdb_breakpoint "thread_function"
+
+    gdb_continue_to_breakpoint "once"
+    gdb_test "inferior 1" "Switching to inferior 1 .*"
+    gdb_continue_to_breakpoint "twice"
+
+    gdb_test "info threads" \
+	[multi_line \
+	     "  1\.1 .*" \
+	     "\\* 1\.2 .* thread_function .* at .*$srcfile:.*" \
+	     "  2\.1 .*" \
+	     "  2\.2 .* thread_function .* at .*$srcfile:.*"] \
+	"info threads again"
+
+    # Same, but show the global ID.
+    gdb_test "info threads -gid" \
+	[multi_line \
+	     "  1\.1 +1 +.*" \
+	     "\\* 1\.2 +4 +.* thread_function .* at .*$srcfile:.*" \
+	     "  2\.1 +2 +.*" \
+	     "  2\.2 +3 +.* thread_function .* at .*$srcfile:.*"]
+
+    # Confirm the convenience variables show the expected numbers.
+    gdb_test "p \$_thread == 2" " = 1"
+    gdb_test "p \$_gthread == 4" " = 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\\."
+
+    # If Python is configured, check that InferiorThread.global_num
+    # returns the expected number.
+    if { ![skip_python_tests] } {
+	gdb_py_test_silent_cmd "python t0 = gdb.selected_thread ()" "test gdb.selected_thread" 1
+	gdb_test "python print ('result = %s' % t0.num)" " = 2" "test InferiorThread.num"
+	gdb_test "python print ('result = %s' % t0.global_num)" " = 4" "test InferiorThread.global_num"
+    }
+}
+
+# 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.
+    gdb_test "info threads" \
+	[multi_line \
+	     "\\* 1 .*" \
+	     "  2 .* thread_function .* at .*$srcfile:.*"]
+
+    gdb_test "thread" "Current thread is 1 .*"
+}
+
+# Add another inferior and remove inferior 1.  Since even though
+# there's a single inferior, it's number is not 1, GDB should show
+# extended thread IDs that include the inferior number.
+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.
+    gdb_test "info threads" \
+	[multi_line \
+	     "\\* 1\.1 .*" \
+	     "  1\.2 .* thread_function .* at .*$srcfile:.*"] \
+	"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 show the inferior number.
+    gdb_test "info threads" " 3\.1 .* main .* at .*$srcfile:.*" \
+	"info threads with single inferior"
+
+    gdb_test "thread" "Current thread is 3\.1 .*" "thread again"
+}
diff --git a/gdb/testsuite/gdb.python/py-infthread.exp b/gdb/testsuite/gdb.python/py-infthread.exp
index 182254b..c28903f 100644
--- a/gdb/testsuite/gdb.python/py-infthread.exp
+++ b/gdb/testsuite/gdb.python/py-infthread.exp
@@ -41,9 +41,13 @@  if ![runto_main] then {
 
 gdb_py_test_silent_cmd "python t0 = gdb.selected_thread ()" "test gdb.selected_thread" 1
 gdb_test "python print (t0)" "\\<gdb.InferiorThread object at 0x\[\[:xdigit:\]\]+>" "verify InferiorThread object"
-gdb_test "python print ('result = %s' % t0.num)" " = \[0-9\]+" "test Inferior.num"
+gdb_test "python print ('result = %s' % t0.num)" " = 1" "test InferiorThread.num"
+gdb_test "python print ('result = %s' % t0.global_num)" " = 1" "test InferiorThread.global_num"
 gdb_test "python print ('result = %s' % str (t0.ptid))" " = \\(\[0-9\]+, \[0-9\]+, \[0-9\]+\\)" "test InferiorThread.ptid"
 
+gdb_py_test_silent_cmd "python i0 = t0.inferior" "test InferiorThread.inferior" 1
+gdb_test "python print ('result = %s' % i0.num)" " = 1" "test Inferior.num"
+
 gdb_py_test_silent_cmd "python name = gdb.selected_thread().name" \
     "get supplied name of current thread" 1
 gdb_py_test_silent_cmd "python gdb.selected_thread().name = 'hibob'" \
diff --git a/gdb/testsuite/gdb.threads/thread-specific.exp b/gdb/testsuite/gdb.threads/thread-specific.exp
index d975eed..bba0833 100644
--- a/gdb/testsuite/gdb.threads/thread-specific.exp
+++ b/gdb/testsuite/gdb.threads/thread-specific.exp
@@ -66,7 +66,10 @@  clean_restart ${binfile}
 gdb_test_no_output "set print sevenbit-strings"
 gdb_test_no_output "set width 0"
 
+# As this test only runs a single inferior, $_thread and $_gthread
+# should match throughout.
 gdb_test {print $_thread} ".* = 0" "thread var when not running"
+gdb_test {print $_gthread} ".* = 0" "gthread var when not running"
 
 runto_main
 
@@ -82,6 +85,7 @@  if {[llength $threads] == 0} {
 }
 
 gdb_test {print $_thread} ".* = [lindex $threads 0]" "thread var in main"
+gdb_test {print $_gthread} ".* = [lindex $threads 0]" "gthread var in main"
 
 gdb_test_multiple "break $line thread [lindex $threads 0]" \
   "breakpoint $line main thread" {
@@ -120,6 +124,7 @@  if { $this_breakpoint != -1 } {
 
 if { $this_thread != -1 } {
     gdb_test {print $_thread} ".* = $this_thread" "thread var at break"
+    gdb_test {print $_gthread} ".* = $this_thread" "gthread var at break"
 } else {
     untested "thread var at break"
 }
diff --git a/gdb/thread.c b/gdb/thread.c
index 7d2232f..919e867 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -182,7 +182,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_id);
 
   bpstat_clear (&tp->control.stop_bpstat);
 
@@ -230,12 +230,18 @@  init_thread_list (void)
    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_id = ++highest_thread_num;
+  tp->inf = inf;
+  tp->per_inf_id = ++inf->highest_thread_num;
 
   if (thread_list == NULL)
     thread_list = tp;
@@ -260,6 +266,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 +285,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 +309,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 +489,24 @@  delete_thread_silent (ptid_t ptid)
 }
 
 struct thread_info *
-find_thread_id (int num)
+find_thread_global_id (int id)
 {
   struct thread_info *tp;
 
   for (tp = thread_list; tp; tp = tp->next)
-    if (tp->num == num)
+    if (tp->global_id == id)
+      return tp;
+
+  return NULL;
+}
+
+static struct thread_info *
+find_thread_id (struct inferior *inf, int id)
+{
+  struct thread_info *tp;
+
+  for (tp = thread_list; tp; tp = tp->next)
+    if (tp->inf == inf && tp->per_inf_id == id)
       return tp;
 
   return NULL;
@@ -548,38 +568,38 @@  thread_count (void)
 }
 
 int
-valid_thread_id (int num)
+valid_global_thread_id (int id)
 {
   struct thread_info *tp;
 
   for (tp = thread_list; tp; tp = tp->next)
-    if (tp->num == num)
+    if (tp->global_id == 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_id;
 
   return 0;
 }
 
 ptid_t
-thread_id_to_pid (int num)
+global_thread_id_to_ptid (int num)
 {
-  struct thread_info *thread = find_thread_id (num);
+  struct thread_info *thread = find_thread_global_id (num);
 
   if (thread)
     return thread->ptid;
   else
-    return pid_to_ptid (-1);
+    return minus_one_ptid;
 }
 
 int
@@ -604,7 +624,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_id < ret->global_id)
 	ret = tp;
 
   return ret;
@@ -688,10 +708,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_id;
 
       num++;
-      ui_out_field_int (uiout, "thread-id", tp->num);
+      ui_out_field_int (uiout, "thread-id", tp->global_id);
     }
 
   do_cleanups (cleanup_chain);
@@ -1105,25 +1125,51 @@  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)
+static int
+should_print_thread (const char *requested_threads, int global_ids,
+		     int pid, struct thread_info *thr)
+{
+  if (pid != -1 && ptid_get_pid (thr->ptid) != pid
+      && requested_threads != NULL && *requested_threads != '\0')
+    error (_("Requested thread not found in requested process"));
+
+  if (requested_threads != NULL && *requested_threads != '\0')
+    {
+      int id = global_ids ? thr->global_id : thr->per_inf_id;
+
+      if (number_is_in_list (requested_threads, id))
+	{
+	  if (pid != -1 && ptid_get_pid (thr->ptid) != pid)
+	    error (_("Requested thread not found in requested process"));
+
+	  return 1;
+	}
+
+      return 0;
+    }
+  else if (pid != -1)
+    return ptid_get_pid (thr->ptid) == pid;
+  else if (thr->state == THREAD_EXITED)
+    return 0;
+  else
+    return 1;
+}
+
+/* Like print_thread_info, but in addition, GLOBAL_IDS specified
+   whether REQUESTED_THREADS indicated 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;
 
   update_thread_list ();
   current_ptid = inferior_ptid;
@@ -1142,13 +1188,7 @@  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, global_ids, pid, tp))
 	    continue;
 
 	  ++n_threads;
@@ -1165,34 +1205,37 @@  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);
     }
 
+  ALL_INFERIORS (inf)
+    {
+
   for (tp = thread_list; tp; tp = tp->next)
     {
       struct cleanup *chain2;
       int core;
 
-      if (!number_is_in_list (requested_threads, tp->num))
+      if (inf->pid != ptid_get_pid (tp->ptid))
 	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_id;
 
-      if (tp->state == THREAD_EXITED)
+      if (!should_print_thread (requested_threads, global_ids, pid, tp))
 	continue;
 
       chain2 = make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
@@ -1213,7 +1256,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_id);
 
       /* For the CLI, we stuff everything into the target-id field.
 	 This is a gross hack to make the output come out looking
@@ -1282,6 +1329,7 @@  print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
 
       do_cleanups (chain2);
     }
+    }
 
   /* Restores the current thread and the frame selected before
      the "info threads" command.  */
@@ -1289,27 +1337,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
+The current thread <Thread ID %s> has terminated.  See `help thread'.\n",
+			print_thread_id (inferior_thread ()));
+      else if (thread_list != NULL
 	       && ptid_equal (current_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.  */
+
+void
+print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
+{
+  print_thread_info_1 (uiout, requested_threads, 1, pid, 0);
+}
 
-   Optional ARG is a thread id, or list of thread ids.
+/* 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 +1374,16 @@  No selected thread.  See `help thread'.\n");
 static void
 info_threads_command (char *arg, int from_tty)
 {
-  print_thread_info (current_uiout, arg, -1);
+  int show_global_ids = 0;
+
+  if (arg != NULL
+      && check_for_argument (&arg, "-gid", sizeof ("-gid") - 1))
+    {
+      arg = skip_spaces (arg);
+      show_global_ids = 1;
+    }
+
+  print_thread_info_1 (current_uiout, arg, 0, -1, show_global_ids);
 }
 
 /* See gdbthread.h.  */
@@ -1593,18 +1658,24 @@  make_cleanup_restore_current_thread (void)
 
 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, and then by per-inferior ID).  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;
 
-  return ((((*ap)->num > (*bp)->num) - ((*ap)->num < (*bp)->num))
+  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 (((a->per_inf_id > b->per_inf_id)
+	   - (a->per_inf_id < b->per_inf_id))
 	  * (tp_array_compar_ascending ? +1 : -1));
 }
 
@@ -1678,8 +1749,8 @@  thread_apply_all_command (char *cmd, int from_tty)
         if (thread_alive (tp_array[k]))
           {
             switch_to_thread (tp_array[k]->ptid);
-            printf_filtered (_("\nThread %d (%s):\n"), 
-			     tp_array[k]->num,
+            printf_filtered (_("\nThread %s (%s):\n"),
+			     print_thread_id (tp_array[k]),
 			     target_pid_to_str (inferior_ptid));
             execute_command (cmd, from_tty);
 
@@ -1691,6 +1762,18 @@  thread_apply_all_command (char *cmd, int from_tty)
   do_cleanups (old_chain);
 }
 
+const char *
+print_thread_id (struct thread_info *thr)
+{
+  char *s = get_print_cell ();
+
+  if (inferior_list->next != NULL || inferior_list->num != 1)
+    xsnprintf (s, PRINT_CELL_SIZE, "%d.%d", thr->inf->num, thr->per_inf_id);
+  else
+    xsnprintf (s, PRINT_CELL_SIZE, "%d", thr->per_inf_id);
+  return s;
+}
+
 static void
 thread_apply_command (char *tidlist, int from_tty)
 {
@@ -1722,7 +1805,7 @@  thread_apply_command (char *tidlist, int from_tty)
 
       make_cleanup_restore_current_thread ();
 
-      tp = find_thread_id (start);
+      tp = find_thread_id (current_inferior (), start);
 
       if (!tp)
 	warning (_("Unknown thread %d."), start);
@@ -1732,7 +1815,7 @@  thread_apply_command (char *tidlist, int from_tty)
 	{
 	  switch_to_thread (tp->ptid);
 
-	  printf_filtered (_("\nThread %d (%s):\n"), tp->num,
+	  printf_filtered (_("\nThread %s (%s):\n"), print_thread_id (tp),
 			   target_pid_to_str (inferior_ptid));
 	  execute_command (cmd, from_tty);
 
@@ -1757,13 +1840,15 @@  thread_command (char *tidstr, int from_tty)
 
       if (target_has_stack)
 	{
+	  struct thread_info *tp = inferior_thread ();
+
 	  if (is_exited (inferior_ptid))
-	    printf_filtered (_("[Current thread is %d (%s) (exited)]\n"),
-			     pid_to_thread_id (inferior_ptid),
+	    printf_filtered (_("[Current thread is %s (%s) (exited)]\n"),
+			     print_thread_id (tp),
 			     target_pid_to_str (inferior_ptid));
 	  else
-	    printf_filtered (_("[Current thread is %d (%s)]\n"),
-			     pid_to_thread_id (inferior_ptid),
+	    printf_filtered (_("[Current thread is %s (%s)]\n"),
+			     print_thread_id (tp),
 			     target_pid_to_str (inferior_ptid));
 	}
       else
@@ -1812,32 +1897,32 @@  thread_find_command (char *arg, int from_tty)
     {
       if (tp->name != NULL && re_exec (tp->name))
 	{
-	  printf_filtered (_("Thread %d has name '%s'\n"),
-			   tp->num, tp->name);
+	  printf_filtered (_("Thread %s has name '%s'\n"),
+			   print_thread_id (tp), tp->name);
 	  match++;
 	}
 
       tmp = target_thread_name (tp);
       if (tmp != NULL && re_exec (tmp))
 	{
-	  printf_filtered (_("Thread %d has target name '%s'\n"),
-			   tp->num, tmp);
+	  printf_filtered (_("Thread %s has target name '%s'\n"),
+			   print_thread_id (tp), tmp);
 	  match++;
 	}
 
       tmp = target_pid_to_str (tp->ptid);
       if (tmp != NULL && re_exec (tmp))
 	{
-	  printf_filtered (_("Thread %d has target id '%s'\n"),
-			   tp->num, tmp);
+	  printf_filtered (_("Thread %s has target id '%s'\n"),
+			   print_thread_id (tp), tmp);
 	  match++;
 	}
 
       tmp = target_extra_thread_info (tp);
       if (tmp != NULL && re_exec (tmp))
 	{
-	  printf_filtered (_("Thread %d has extra info '%s'\n"),
-			   tp->num, tmp);
+	  printf_filtered (_("Thread %s has extra info '%s'\n"),
+			   print_thread_id (tp), tmp);
 	  match++;
 	}
     }
@@ -1856,31 +1941,105 @@  show_print_thread_events (struct ui_file *file, int from_tty,
                     value);
 }
 
-static int
-do_captured_thread_select (struct ui_out *uiout, void *tidstr)
+struct thread_info *
+parse_thread_id (const char *tidstr, const char **end)
 {
-  int num;
+  const char *number = tidstr;
+  const char *dot, *p1;
   struct thread_info *tp;
+  struct inferior *inf;
+  int thr_num;
+  int explicit_inf_id = 0;
 
-  num = value_as_long (parse_and_eval ((const char *) tidstr));
+  dot = strchr (number, '.');
 
-  tp = find_thread_id (num);
+  if (dot != NULL)
+    {
+      /* Parse number to the left of the dot.  */
+      int inf_num;
 
-  if (!tp)
-    error (_("Thread ID %d not known."), num);
+      p1 = number;
+      inf_num = get_number_trailer (&p1, '.');
+      if (inf_num == 0)
+	error (_("Bad thread spec '%s'"), 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)
+    error (_("Bad thread spec '%s'"), number);
+
+  ALL_THREADS (tp)
+    {
+      if (ptid_get_pid (tp->ptid) == inf->pid
+	  && tp->per_inf_id == thr_num)
+	break;
+    }
+
+  if (tp == NULL)
+    {
+      if (inferior_list->next != NULL || inferior_list->num != 1
+	  || 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;
+}
+
+static int
+do_captured_thread_select (struct ui_out *uiout, void *tidstr_v)
+{
+  const char *tidstr = tidstr_v;
+  struct thread_info *tp;
+
+  if (ui_out_is_mi_like_p (uiout))
+    {
+      int num = value_as_long (parse_and_eval (tidstr));
+
+      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_int (uiout, "new-thread-id", pid_to_thread_id (inferior_ptid));
-  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_id);
+  else
+    {
+      ui_out_text (uiout, "[Switching to thread ");
+      ui_out_text (uiout, print_thread_id (inferior_thread ()));
+      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.  */
@@ -1934,17 +2093,44 @@  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 id.  Return a value of
+   0 if no thread is selected.  If GLOBAL is true, return the thread's
+   global ID.  Otherwise return the per-inferior ID.  */
 
 static struct value *
-thread_id_make_value (struct gdbarch *gdbarch, struct internalvar *var,
-		      void *ignore)
+thread_id_make_value_helper (struct gdbarch *gdbarch, int global)
 {
   struct thread_info *tp = find_thread_ptid (inferior_ptid);
+  int int_val;
+
+  if (tp == NULL)
+    int_val = 0;
+  else if (global)
+    int_val = tp->global_id;
+  else
+    int_val = tp->per_inf_id;
 
-  return value_from_longest (builtin_type (gdbarch)->builtin_int,
-			     (tp ? tp->num : 0));
+  return value_from_longest (builtin_type (gdbarch)->builtin_int, int_val);
+}
+
+/* Return a new value for the selected thread's id.  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,
+                      void *ignore)
+{
+  return thread_id_make_value_helper (gdbarch, 0);
+}
+
+/* Return a new value for the selected thread's global id.  Return a
+   value of 0 if no thread is selected, or no threads exist.  */
+
+static struct value *
+global_thread_id_make_value (struct gdbarch *gdbarch, struct internalvar *var,
+                             void *ignore)
+{
+  return thread_id_make_value_helper (gdbarch, 1);
 }
 
 /* Commands with a prefix of `thread'.  */
@@ -1959,6 +2145,15 @@  static const struct internalvar_funcs thread_funcs =
   NULL
 };
 
+/* Implementation of `gthread' variable.  */
+
+static const struct internalvar_funcs gthread_funcs =
+{
+  global_thread_id_make_value,
+  NULL,
+  NULL
+};
+
 void
 _initialize_thread (void)
 {
@@ -1966,9 +2161,10 @@  _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."));
+Usage: info threads [-gid] [ID]...\n\
+-gid: Show global thread IDs.\n\
+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\
@@ -2011,6 +2207,7 @@  Show printing of thread events (such as thread start and exit)."), NULL,
          &setprintlist, &showprintlist);
 
   create_internalvar_type_lazy ("_thread", &thread_funcs, NULL);
+  create_internalvar_type_lazy ("_gthread", &gthread_funcs, NULL);
 
   observer_attach_thread_ptid_changed (restore_current_thread_ptid_changed);
 }
diff --git a/gdb/varobj.c b/gdb/varobj.c
index 0b19d84..8118cea 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 belong 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);