Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH] Per-inferior thread IDs
@ 2015-12-15 19:05 Pedro Alves
  2015-12-15 19:39 ` Eli Zaretskii
  2015-12-18 17:53 ` [PATCH] Per-inferior thread IDs Joel Brobecker
  0 siblings, 2 replies; 10+ messages in thread
From: Pedro Alves @ 2015-12-15 19:05 UTC (permalink / raw)
  To: gdb-patches

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

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
+};
+
 \f
 
 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);
-- 
1.9.3


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Per-inferior thread IDs
  2015-12-15 19:05 [PATCH] Per-inferior thread IDs Pedro Alves
@ 2015-12-15 19:39 ` Eli Zaretskii
  2015-12-17 13:17   ` Pedro Alves
  2015-12-18 17:53 ` [PATCH] Per-inferior thread IDs Joel Brobecker
  1 sibling, 1 reply; 10+ messages in thread
From: Eli Zaretskii @ 2015-12-15 19:39 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

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


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Per-inferior thread IDs
  2015-12-15 19:39 ` Eli Zaretskii
@ 2015-12-17 13:17   ` Pedro Alves
  2015-12-17 16:54     ` Eli Zaretskii
  0 siblings, 1 reply; 10+ messages in thread
From: Pedro Alves @ 2015-12-17 13:17 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 12/15/2015 07:39 PM, Eli Zaretskii wrote:
>> 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").

Good catch, thanks.  I copied that mistake from the $_thread documentation.
Also, I realized that $_thread was not documented in the Convenience Vars
node.  I fixed that now, and also mentioned the new $_inferior/$_gthread
variables there.

> 
> 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?

You're right.  How about per-inferior number to refer to the number
in the inferior, and use thread ID to generically refer to the
inferior num + thread num pair?

I'm now calling the expanded "inf-num.thr-num" thread ID form
the "qualified" thread ID.

See updated doc patch below.  I found other places that talked about
thread number and updated them to say thread ID instead.
E.g., the "thread @var{threadno}" command now says "thread @var{thread-id}".

I think it's much less confusing now.

diff --git c/gdb/NEWS w/gdb/NEWS
index 9ca7f49..b4ff971 100644
--- c/gdb/NEWS
+++ w/gdb/NEWS
@@ -3,6 +3,45 @@
 
 *** Changes since GDB 7.10
 
+* Per-inferior thread numbers
+
+  Thread numbers are now per inferior instead of global.  If you're
+  debugging multiple inferiors, GDB displays thread IDs using 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 qualified form as
+  well:
+
+     (gdb) thread 2.1
+     [Switching to thread 2.1 (Thread 0x7ffff7fc2740 (LWP 8157))] (running)
+     (gdb)
+
+  As consequence, thread numbers 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 numbers in
+  previous releases.
+
+  For backwards compatibility, MI's thread IDs always refer to global
+  IDs.
+
+* 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 number.
+  ** 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 c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
index bb68e21..b0e4106 100644
--- c/gdb/doc/gdb.texinfo
+++ w/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.
+@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
@@ -2832,9 +2838,9 @@ programs:
 
 @itemize @bullet
 @item automatic notification of new threads
-@item @samp{thread @var{threadno}}, a command to switch among threads
+@item @samp{thread @var{thread-id}}, a command to switch among threads
 @item @samp{info threads}, a command to inquire about existing threads
-@item @samp{thread apply [@var{threadno}] [@var{all}] @var{args}},
+@item @samp{thread apply [@var{thread-id}] [@var{all}] @var{args}},
 a command to apply a command to a list of threads
 @item thread-specific breakpoints
 @item @samp{set print thread-events}, which controls printing of 
@@ -2880,10 +2886,34 @@ further qualifier.
 @c         multithread systems permit starting a program with multiple
 @c         threads ab initio?
 
-@cindex thread number
+@anchor{thread numbers}
+@cindex thread number, per inferior
 @cindex thread identifier (GDB)
-For debugging purposes, @value{GDBN} associates its own thread
-number---always a single integer---with each thread in your program.
+For debugging purposes, @value{GDBN} associates its own thread number
+---always a single integer---with each thread of an inferior.  This
+number is unique between all threads of an inferior, but not unique
+between threads of different inferiors.
+
+You can refer to a given thread in an inferior using the qualified
+@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.
+
+@anchor{global thread numbers}
+@cindex global thread number
+@cindex global thread identifier (GDB)
+In addition to a @emph{per-inferior} number, each thread is also
+assigned a unique @emph{global} number, also a single integer.  Unlike
+the thread number component of the thread ID, no two threads have the
+same global ID, even when you're debugging multiple inferiors.
 
 From @value{GDBN}'s perspective, a process always has at least one
 thread.  In other words, @value{GDBN} assigns a thread number to the
@@ -2899,7 +2929,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 +2964,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:
 
@@ -2941,13 +2990,13 @@ Display info on Solaris user threads.
 @end table
 
 @table @code
-@kindex thread @var{threadno}
-@item thread @var{threadno}
-Make thread number @var{threadno} the current thread.  The command
-argument @var{threadno} is the internal @value{GDBN} thread number, as
+@kindex thread @var{thread-id}
+@item thread @var{thread-id}
+Make thread ID @var{thread-id} the current thread.  The command
+argument @var{thread-id} is the internal @value{GDBN} thread ID, 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:
+@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,24 +3011,33 @@ 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.  @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
-@item thread apply [@var{threadno} | all [-ascending]] @var{command}
+@item thread apply [@var{thread-id} | all [-ascending]] @var{command}
 The @code{thread apply} command allows you to apply the named
 @var{command} to one or more threads.  Specify the numbers of the
 threads that you want affected with the command argument
-@var{threadno}.  It can be a single thread number, one of the numbers
-shown in the first field of the @samp{info threads} display; or it
-could be a range of thread numbers, as in @code{2-4}.  To apply
-a command to all threads in descending order, type @kbd{thread apply all
-@var{command}}.  To apply a command to all threads in ascending order,
-type @kbd{thread apply all -ascending @var{command}}.
+@var{thread-id}.  It can be a single thread ID, as shown in the first
+field of the @samp{info threads} display, with or without an inferior
+qualifier (e.g., @samp{2.1} or @samp{1}); or it could be a range of
+thread numbers, as in @code{2-4}.  To apply a command to all threads
+in descending order, type @kbd{thread apply all @var{command}}.  To
+apply a command to all threads in ascending order, type @kbd{thread
+apply all -ascending @var{command}}.
 
 
 @kindex thread name
@@ -6041,25 +6099,25 @@ breakpoints on all threads, or on a particular thread.
 @table @code
 @cindex breakpoints and threads
 @cindex thread breakpoints
-@kindex break @dots{} thread @var{threadno}
-@item break @var{location} thread @var{threadno}
-@itemx break @var{location} thread @var{threadno} if @dots{}
+@kindex break @dots{} thread @var{thread-id}
+@item break @var{location} thread @var{thread-id}
+@itemx break @var{location} thread @var{thread-id} if @dots{}
 @var{location} specifies source lines; there are several ways of
 writing them (@pxref{Specify Location}), but the effect is always to
 specify some source line.
 
-Use the qualifier @samp{thread @var{threadno}} with a breakpoint command
+Use the qualifier @samp{thread @var{thread-id}} with a breakpoint command
 to specify that you only want @value{GDBN} to stop the program when a
-particular thread reaches this breakpoint.  The @var{threadno} specifier
-is one of the numeric thread identifiers assigned by @value{GDBN}, shown
+particular thread reaches this breakpoint.  The @var{thread-id} specifier
+is one of the thread identifiers assigned by @value{GDBN}, shown
 in the first column of the @samp{info threads} display.
 
-If you do not specify @samp{thread @var{threadno}} when you set a
+If you do not specify @samp{thread @var{thread-id}} when you set a
 breakpoint, the breakpoint applies to @emph{all} threads of your
 program.
 
 You can use the @code{thread} qualifier on conditional breakpoints as
-well; in this case, place @samp{thread @var{threadno}} before or
+well; in this case, place @samp{thread @var{thread-id}} before or
 after the breakpoint condition, like this:
 
 @smallexample
@@ -10378,6 +10436,16 @@ gdbserver that supports the @code{qGetTIBAddr} request.
 @xref{General Query Packets}.
 This variable contains the address of the thread information block.
 
+@item $_inferior
+The number of the current inferior.  @xref{Inferiors and
+Programs, ,Debugging Multiple Inferiors and Programs}.
+
+@item $_thread
+The thread number of the current thread.  @xref{thread numbers}.
+
+@item $_gthread
+The global number of the current thread.  @xref{global thread numbers}.
+
 @end table
 
 @node Convenience Funs
@@ -16120,7 +16188,7 @@ This command prints the ID of the current task.
 
 @item task @var{taskno}
 @cindex Ada task switching
-This command is like the @code{thread @var{threadno}}
+This command is like the @code{thread @var{thread-id}}
 command (@pxref{Threads}).  It switches the context of debugging
 from the current task to the given task.
 
@@ -25357,8 +25425,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 +25979,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 +26042,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 +26080,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 +26341,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 +26814,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 +26905,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 +27556,11 @@ The corresponding @value{GDBN} command is @samp{pwd}.
  -thread-info [ @var{thread-id} ]
 @end smallexample
 
-Reports information about either a specific thread, if 
-the @var{thread-id} parameter is present, or about all
-threads.  When printing information about all threads,
-also reports the current thread.
+Reports information about either a specific thread, if the
+@var{thread-id} parameter is present, or about all threads.
+@var{thread-id} is the thread's global thread ID.  When printing
+information about all threads, also reports the global ID of the
+current thread.
 
 @subsubheading @value{GDBN} Command
 
@@ -27504,7 +27577,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 +27643,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 +27674,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 +27745,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 +28962,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 +29143,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.


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Per-inferior thread IDs
  2015-12-17 13:17   ` Pedro Alves
@ 2015-12-17 16:54     ` Eli Zaretskii
  2015-12-18 18:11       ` Pedro Alves
  0 siblings, 1 reply; 10+ messages in thread
From: Eli Zaretskii @ 2015-12-17 16:54 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Date: Thu, 17 Dec 2015 13:17:04 +0000
> From: Pedro Alves <palves@redhat.com>
> CC: gdb-patches@sourceware.org
> 
> On 12/15/2015 07:39 PM, Eli Zaretskii wrote:
> >> 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").
> 
> Good catch, thanks.  I copied that mistake from the $_thread documentation.
> Also, I realized that $_thread was not documented in the Convenience Vars
> node.  I fixed that now, and also mentioned the new $_inferior/$_gthread
> variables there.

Thanks.

> > 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?
> 
> You're right.  How about per-inferior number to refer to the number
> in the inferior, and use thread ID to generically refer to the
> inferior num + thread num pair?

Sounds good to me, thanks.

I have a few minor comments:

> +* Per-inferior thread numbers
> +
> +  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:

Suggest to use "qualified" instead of "expanded" here, so that the
next text:

> +     (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 qualified form as
> +  well:                                          ^^^^^^^^^^^^^^

will use the same terminology.

> +
> +     (gdb) thread 2.1
> +     [Switching to thread 2.1 (Thread 0x7ffff7fc2740 (LWP 8157))] (running)
> +     (gdb)
> +
> +  As consequence, thread numbers as visible in the $_thread
> +  convenience variable are no longer unique between inferiors.

I stumbled on the "as a convenience" part.  It took me a few seconds
to understand that this refers to the "Thread numbers are now per
inferior instead of global" part at the beginning, rather than to the
later description of "qualified IDs".  So I suggest to say that
explicitly:

  As a consequence of thread numbers being per inferior, the value of
  the $_thread convenience variable are no longer unique between
  inferiors.

Alternatively (maybe even better), move this sentence immediately
after the first one.

> +You can refer to a given thread in an inferior using the qualified
> +@var{inferior-num}.@var{thread-num} syntax, with @var{inferior-num}

Suggest a @cindex entry here about "qualified thread ID".

> +                                If you omit @var{inferior-num}, then
> +@value{GDBN} infers you're referring to a thread of the current
> +inferior.

This begs a question: do you mean "thread .2" or "thread 2" here?
IOW, what does "omit" stand for?

> +@kindex thread @var{thread-id}
> +@item thread @var{thread-id}
> +Make thread ID @var{thread-id} the current thread.  The command
> +argument @var{thread-id} is the internal @value{GDBN} thread ID, as
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>  shown in the first field of the @samp{info threads} display.

Why not use "qualified thread ID" here?

> +The debugger convenience variable @samp{$_thread} contains the
> +per-inferior thread number of the current thread.  You may find this
> +useful in writing breakpoint conditional expressions, command scripts,
> +and so forth.  @xref{Convenience Vars,, Convenience Variables}, for
> +general information on convenience variables.
> +
> +@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.

(There's "See @xref" again.)  You have here almost exactly the same
text twice, complete with 2 identical cross-references to the same
place.  I suggest to have the text only once, and describe both
variables together, like this:
 
 The debugger convenience variables @samp{$_thread} and
 samp{$_gthread} contain, respectively, the per-inferior thread number
 and the global thread number of the current thread.  You may find ...

> +@var{thread-id}.  It can be a single thread ID, as shown in the first
> +field of the @samp{info threads} display, with or without an inferior
> +qualifier (e.g., @samp{2.1} or @samp{1}); or it could be a range of
> +thread numbers, as in @code{2-4}.  To apply a command to all threads
> +in descending order, type @kbd{thread apply all @var{command}}.  To
> +apply a command to all threads in ascending order, type @kbd{thread
> +apply all -ascending @var{command}}.

Can I use a range of qualified IDs, as in "2.1-2.4"?

> -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.
                                  ^^^
"num" is not a word.  Please use "number".


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Per-inferior thread IDs
  2015-12-15 19:05 [PATCH] Per-inferior thread IDs Pedro Alves
  2015-12-15 19:39 ` Eli Zaretskii
@ 2015-12-18 17:53 ` Joel Brobecker
  2015-12-18 18:47   ` Pedro Alves
  1 sibling, 1 reply; 10+ messages in thread
From: Joel Brobecker @ 2015-12-18 17:53 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

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

-- 
Joel


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Per-inferior thread IDs
  2015-12-17 16:54     ` Eli Zaretskii
@ 2015-12-18 18:11       ` Pedro Alves
  2015-12-18 19:36         ` Eli Zaretskii
  2015-12-24 14:56         ` [PATCH] thread ID ranges (Per-inferior thread IDs) Pedro Alves
  0 siblings, 2 replies; 10+ messages in thread
From: Pedro Alves @ 2015-12-18 18:11 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 12/17/2015 04:54 PM, Eli Zaretskii wrote:
>> Date: Thu, 17 Dec 2015 13:17:04 +0000
>> From: Pedro Alves <palves@redhat.com>
>> CC: gdb-patches@sourceware.org
>>
>> On 12/15/2015 07:39 PM, Eli Zaretskii wrote:
>>>> 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").
>>
>> Good catch, thanks.  I copied that mistake from the $_thread documentation.
>> Also, I realized that $_thread was not documented in the Convenience Vars
>> node.  I fixed that now, and also mentioned the new $_inferior/$_gthread
>> variables there.
> 
> Thanks.
> 
>>> 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?
>>
>> You're right.  How about per-inferior number to refer to the number
>> in the inferior, and use thread ID to generically refer to the
>> inferior num + thread num pair?
> 
> Sounds good to me, thanks.
> 
> I have a few minor comments:
> 
>> +* Per-inferior thread numbers
>> +
>> +  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:
> 
> Suggest to use "qualified" instead of "expanded" here, so that the
> next text:

Done.

> 
>> +     (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 qualified form as
>> +  well:                                          ^^^^^^^^^^^^^^
> 
> will use the same terminology.
> 
>> +
>> +     (gdb) thread 2.1
>> +     [Switching to thread 2.1 (Thread 0x7ffff7fc2740 (LWP 8157))] (running)
>> +     (gdb)
>> +
>> +  As consequence, thread numbers as visible in the $_thread
>> +  convenience variable are no longer unique between inferiors.
> 
> I stumbled on the "as a convenience" part.  It took me a few seconds
> to understand that this refers to the "Thread numbers are now per
> inferior instead of global" part at the beginning, rather than to the
> later description of "qualified IDs".  So I suggest to say that
> explicitly:
> 
>   As a consequence of thread numbers being per inferior, the value of
>   the $_thread convenience variable are no longer unique between
>   inferiors.
> 
> Alternatively (maybe even better), move this sentence immediately
> after the first one.

OK, I reordered.

> 
>> +You can refer to a given thread in an inferior using the qualified
>> +@var{inferior-num}.@var{thread-num} syntax, with @var{inferior-num}
> 
> Suggest a @cindex entry here about "qualified thread ID".

Added.  Also added a @dfn while at it:

 You can refer to a given thread in an inferior using the qualified
 @var{inferior-num}.@var{thread-num} syntax, also known as
 @dfn{qualified thread ID}, with @var{inferior-num} being the inferior

Likewise for the global thread ID part.

> 
>> +                                If you omit @var{inferior-num}, then
>> +@value{GDBN} infers you're referring to a thread of the current
>> +inferior.
> 
> This begs a question: do you mean "thread .2" or "thread 2" here?
> IOW, what does "omit" stand for?

I added an example.

> 
>> +@kindex thread @var{thread-id}
>> +@item thread @var{thread-id}
>> +Make thread ID @var{thread-id} the current thread.  The command
>> +argument @var{thread-id} is the internal @value{GDBN} thread ID, as
>                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>  shown in the first field of the @samp{info threads} display.
> 
> Why not use "qualified thread ID" here?

This is not always inferior-qualified.  It depends on whether you
created a second inferior, as described earlier in the thread IDs
description:

 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.

Should I drop "internal", perhaps?  Currently (unpatched) it says:

-Make thread number @var{threadno} the current thread.  The command
-argument @var{threadno} is the internal @value{GDBN} thread number, as

In any case, I've added an example now:

+Make thread ID @var{thread-id} the current thread.  The command
+argument @var{thread-id} is the internal @value{GDBN} thread ID, as
+shown in the first field of the @samp{info threads} display, with or
+without an inferior qualifier (e.g., @samp{2.1} or @samp{1}).


> 
>> +The debugger convenience variable @samp{$_thread} contains the
>> +per-inferior thread number of the current thread.  You may find this
>> +useful in writing breakpoint conditional expressions, command scripts,
>> +and so forth.  @xref{Convenience Vars,, Convenience Variables}, for
>> +general information on convenience variables.
>> +
>> +@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.
> 
> (There's "See @xref" again.)  You have here almost exactly the same
> text twice, complete with 2 identical cross-references to the same
> place.  I suggest to have the text only once, and describe both
> variables together, like this:
>  
>  The debugger convenience variables @samp{$_thread} and
>  samp{$_gthread} contain, respectively, the per-inferior thread number
>  and the global thread number of the current thread.  You may find ...

Thanks!  I used it.

> 
>> +@var{thread-id}.  It can be a single thread ID, as shown in the first
>> +field of the @samp{info threads} display, with or without an inferior
>> +qualifier (e.g., @samp{2.1} or @samp{1}); or it could be a range of
>> +thread numbers, as in @code{2-4}.  To apply a command to all threads
>> +in descending order, type @kbd{thread apply all @var{command}}.  To
>> +apply a command to all threads in ascending order, type @kbd{thread
>> +apply all -ascending @var{command}}.
> 
> Can I use a range of qualified IDs, as in "2.1-2.4"?

I was thinking of adding support for ranges of threads in the "2.1-4"
form, that is, I didn't think it'd make sense to cross inferior,
like "2.1-3.3", but I haven't implemented it yet.

> 
>> -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.
>                                   ^^^
> "num" is not a word.  Please use "number".
> 

Done.

Thank you very much for the review and suggestions thus far.
Here's an updated docs patch.  Let me know how it looks.

diff --git c/gdb/NEWS w/gdb/NEWS
index 484d98d..e4791cd 100644
--- c/gdb/NEWS
+++ w/gdb/NEWS
@@ -3,6 +3,46 @@
 
 *** Changes since GDB 7.10
 
+* Per-inferior thread numbers
+
+  Thread numbers are now per inferior instead of global.  If you're
+  debugging multiple inferiors, GDB displays thread IDs using a
+  qualified INF_NUM.THR_NUM form.  For example:
+
+     (gdb) info threads
+       Id   Target Id         Frame
+       1.1  Thread 0x7ffff7fc2740 (LWP 8155) (running)
+       1.2  Thread 0x7ffff7fc1700 (LWP 8168) (running)
+     * 2.1  Thread 0x7ffff7fc2740 (LWP 8157) (running)
+       2.2  Thread 0x7ffff7fc1700 (LWP 8190) (running)
+
+  As consequence, thread numbers as visible in the $_thread
+  convenience variable and in Python's InferiorThread.num attribute
+  are no longer unique between inferiors.
+
+  GDB now maintains a second thread ID per thread, referred to as the
+  global thread ID, which is the new equivalent of thread numbers in
+  previous releases.  See also $_gthread below.
+
+  For backwards compatibility, MI's thread IDs always refer to global
+  IDs.
+
+* Commands that accept thread IDs now accept the qualified
+  INF_NUM.THR_NUM form as well.  For example:
+
+     (gdb) thread 2.1
+     [Switching to thread 2.1 (Thread 0x7ffff7fc2740 (LWP 8157))] (running)
+     (gdb)
+
+* 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.
@@ -148,6 +188,15 @@ 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 number.
+     See "Per-inferior thread numbers" above.
+  ** 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 c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
index 7cc1b35..d5a43fb 100644
--- c/gdb/doc/gdb.texinfo
+++ w/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.
+@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
@@ -2832,9 +2838,9 @@ programs:
 
 @itemize @bullet
 @item automatic notification of new threads
-@item @samp{thread @var{threadno}}, a command to switch among threads
+@item @samp{thread @var{thread-id}}, a command to switch among threads
 @item @samp{info threads}, a command to inquire about existing threads
-@item @samp{thread apply [@var{threadno}] [@var{all}] @var{args}},
+@item @samp{thread apply [@var{thread-id}] [@var{all}] @var{args}},
 a command to apply a command to a list of threads
 @item thread-specific breakpoints
 @item @samp{set print thread-events}, which controls printing of 
@@ -2880,15 +2886,49 @@ further qualifier.
 @c         multithread systems permit starting a program with multiple
 @c         threads ab initio?
 
-@cindex thread number
+@anchor{thread numbers}
+@cindex thread number, per inferior
 @cindex thread identifier (GDB)
-For debugging purposes, @value{GDBN} associates its own thread
-number---always a single integer---with each thread in your program.
+For debugging purposes, @value{GDBN} associates its own thread number
+---always a single integer---with each thread of an inferior.  This
+number is unique between all threads of an inferior, but not unique
+between threads of different inferiors.
+
+@cindex qualified thread ID
+You can refer to a given thread in an inferior using the qualified
+@var{inferior-num}.@var{thread-num} syntax, also known as
+@dfn{qualified thread ID}, with @var{inferior-num} being the inferior
+number and @var{thread-num} being the thread number of the given
+inferior.  For example, thread @code{2.3} refers to thread number 3 of
+inferior 2.  If you omit @var{inferior-num} (e.g., @code{thread 3}),
+then @value{GDBN} infers you're referring to a thread of the current
+inferior.
+
+Until you create a second inferior, @value{GDBN} does not show the
+@var{inferior-num} part of thread IDs, even though you can always use
+the full @var{inferior-num}.@var{thread-num} form to refer to threads
+of inferior 1, the initial inferior.
+
+@anchor{global thread numbers}
+@cindex global thread number
+@cindex global thread identifier (GDB)
+In addition to a @emph{per-inferior} number, each thread is also
+assigned a unique @emph{global} number, also known as @dfn{global
+thread ID}, a single integer.  Unlike the thread number component of
+the thread ID, no two threads have the same global ID, even when
+you're debugging multiple inferiors.
 
 From @value{GDBN}'s perspective, a process always has at least one
 thread.  In other words, @value{GDBN} assigns a thread number to the
 program's ``main thread'' even if the program is not multi-threaded.
 
+The debugger convenience variables @samp{$_thread} and
+@samp{$_gthread} contain, respectively, the per-inferior thread number
+and the global thread number of the current thread.  You may find this
+useful in writing breakpoint conditional expressions, command scripts,
+and so forth.  @xref{Convenience Vars,, Convenience Variables}, for
+general information on convenience variables.
+
 @table @code
 @kindex info threads
 @item info threads @r{[}@var{id}@dots{}@r{]}
@@ -2899,7 +2939,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 +2974,22 @@ For example,
     at threadtest.c:68
 @end smallexample
 
+If you're debugging multiple inferiors, @value{GDBN} displays thread
+IDs using the qualified @var{inferior-num}.@var{thread-num} format.
+Otherwise, only @var{thread-num} is shown.
+
+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:
 
@@ -2941,13 +3001,15 @@ Display info on Solaris user threads.
 @end table
 
 @table @code
-@kindex thread @var{threadno}
-@item thread @var{threadno}
-Make thread number @var{threadno} the current thread.  The command
-argument @var{threadno} is the internal @value{GDBN} thread number, as
-shown in the first field of the @samp{info threads} display.
-@value{GDBN} responds by displaying the system identifier of the thread
-you selected, and its current stack frame summary:
+@kindex thread @var{thread-id}
+@item thread @var{thread-id}
+Make thread ID @var{thread-id} the current thread.  The command
+argument @var{thread-id} is the internal @value{GDBN} thread ID, as
+shown in the first field of the @samp{info threads} display, with or
+without an inferior qualifier (e.g., @samp{2.1} or @samp{1}).
+
+@value{GDBN} responds by displaying the system identifier of the
+thread you selected, and its current stack frame summary:
 
 @smallexample
 (@value{GDBP}) thread 2
@@ -2961,25 +3023,19 @@ As with the @samp{[New @dots{}]} message, the form of the text after
 @samp{Switching to} depends on your system's conventions for identifying
 threads.
 
-@vindex $_thread@r{, convenience variable}
-The debugger convenience variable @samp{$_thread} contains the number
-of the current thread.  You may find this useful in writing breakpoint
-conditional expressions, command scripts, and so forth.  See
-@xref{Convenience Vars,, Convenience Variables}, for general
-information on convenience variables.
-
 @kindex thread apply
 @cindex apply command to several threads
-@item thread apply [@var{threadno} | all [-ascending]] @var{command}
+@item thread apply [@var{thread-id} | all [-ascending]] @var{command}
 The @code{thread apply} command allows you to apply the named
 @var{command} to one or more threads.  Specify the numbers of the
 threads that you want affected with the command argument
-@var{threadno}.  It can be a single thread number, one of the numbers
-shown in the first field of the @samp{info threads} display; or it
-could be a range of thread numbers, as in @code{2-4}.  To apply
-a command to all threads in descending order, type @kbd{thread apply all
-@var{command}}.  To apply a command to all threads in ascending order,
-type @kbd{thread apply all -ascending @var{command}}.
+@var{thread-id}.  It can be a single thread ID, as shown in the first
+field of the @samp{info threads} display, with or without an inferior
+qualifier (e.g., @samp{2.1} or @samp{1}); or it could be a range of
+thread numbers, as in @code{2-4}.  To apply a command to all threads
+in descending order, type @kbd{thread apply all @var{command}}.  To
+apply a command to all threads in ascending order, type @kbd{thread
+apply all -ascending @var{command}}.
 
 
 @kindex thread name
@@ -6045,25 +6101,25 @@ breakpoints on all threads, or on a particular thread.
 @table @code
 @cindex breakpoints and threads
 @cindex thread breakpoints
-@kindex break @dots{} thread @var{threadno}
-@item break @var{location} thread @var{threadno}
-@itemx break @var{location} thread @var{threadno} if @dots{}
+@kindex break @dots{} thread @var{thread-id}
+@item break @var{location} thread @var{thread-id}
+@itemx break @var{location} thread @var{thread-id} if @dots{}
 @var{location} specifies source lines; there are several ways of
 writing them (@pxref{Specify Location}), but the effect is always to
 specify some source line.
 
-Use the qualifier @samp{thread @var{threadno}} with a breakpoint command
+Use the qualifier @samp{thread @var{thread-id}} with a breakpoint command
 to specify that you only want @value{GDBN} to stop the program when a
-particular thread reaches this breakpoint.  The @var{threadno} specifier
-is one of the numeric thread identifiers assigned by @value{GDBN}, shown
+particular thread reaches this breakpoint.  The @var{thread-id} specifier
+is one of the thread identifiers assigned by @value{GDBN}, shown
 in the first column of the @samp{info threads} display.
 
-If you do not specify @samp{thread @var{threadno}} when you set a
+If you do not specify @samp{thread @var{thread-id}} when you set a
 breakpoint, the breakpoint applies to @emph{all} threads of your
 program.
 
 You can use the @code{thread} qualifier on conditional breakpoints as
-well; in this case, place @samp{thread @var{threadno}} before or
+well; in this case, place @samp{thread @var{thread-id}} before or
 after the breakpoint condition, like this:
 
 @smallexample
@@ -10382,6 +10438,16 @@ gdbserver that supports the @code{qGetTIBAddr} request.
 @xref{General Query Packets}.
 This variable contains the address of the thread information block.
 
+@item $_inferior
+The number of the current inferior.  @xref{Inferiors and
+Programs, ,Debugging Multiple Inferiors and Programs}.
+
+@item $_thread
+The thread number of the current thread.  @xref{thread numbers}.
+
+@item $_gthread
+The global number of the current thread.  @xref{global thread numbers}.
+
 @end table
 
 @node Convenience Funs
@@ -16124,7 +16190,7 @@ This command prints the ID of the current task.
 
 @item task @var{taskno}
 @cindex Ada task switching
-This command is like the @code{thread @var{threadno}}
+This command is like the @code{thread @var{thread-id}}
 command (@pxref{Threads}).  It switches the context of debugging
 from the current task to the given task.
 
@@ -25469,8 +25535,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
@@ -26023,15 +26089,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
@@ -26085,8 +26152,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
@@ -26122,7 +26190,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}"
@@ -26383,7 +26451,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
@@ -26856,7 +26924,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
@@ -26946,7 +27015,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
@@ -27596,10 +27666,11 @@ The corresponding @value{GDBN} command is @samp{pwd}.
  -thread-info [ @var{thread-id} ]
 @end smallexample
 
-Reports information about either a specific thread, if 
-the @var{thread-id} parameter is present, or about all
-threads.  When printing information about all threads,
-also reports the current thread.
+Reports information about either a specific thread, if the
+@var{thread-id} parameter is present, or about all threads.
+@var{thread-id} is the thread's global thread ID.  When printing
+information about all threads, also reports the global ID of the
+current thread.
 
 @subsubheading @value{GDBN} Command
 
@@ -27616,7 +27687,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.
@@ -27682,8 +27753,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.
@@ -27712,8 +27784,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 number @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.
@@ -27782,7 +27855,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
@@ -28998,7 +29072,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
@@ -29179,8 +29253,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.


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Per-inferior thread IDs
  2015-12-18 17:53 ` [PATCH] Per-inferior thread IDs Joel Brobecker
@ 2015-12-18 18:47   ` Pedro Alves
  0 siblings, 0 replies; 10+ messages in thread
From: Pedro Alves @ 2015-12-18 18:47 UTC (permalink / raw)
  To: Joel Brobecker; +Cc: gdb-patches

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


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] Per-inferior thread IDs
  2015-12-18 18:11       ` Pedro Alves
@ 2015-12-18 19:36         ` Eli Zaretskii
  2015-12-24 14:56         ` [PATCH] thread ID ranges (Per-inferior thread IDs) Pedro Alves
  1 sibling, 0 replies; 10+ messages in thread
From: Eli Zaretskii @ 2015-12-18 19:36 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Date: Fri, 18 Dec 2015 18:11:06 +0000
> From: Pedro Alves <palves@redhat.com>
> CC: gdb-patches@sourceware.org
> 
> >> +@kindex thread @var{thread-id}
> >> +@item thread @var{thread-id}
> >> +Make thread ID @var{thread-id} the current thread.  The command
> >> +argument @var{thread-id} is the internal @value{GDBN} thread ID, as
> >                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> >>  shown in the first field of the @samp{info threads} display.
> > 
> > Why not use "qualified thread ID" here?
> 
> This is not always inferior-qualified.  It depends on whether you
> created a second inferior, as described earlier in the thread IDs
> description:
> 
>  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.
> 
> Should I drop "internal", perhaps?

Yes, I think so.

> In any case, I've added an example now:
> 
> +Make thread ID @var{thread-id} the current thread.  The command
> +argument @var{thread-id} is the internal @value{GDBN} thread ID, as
> +shown in the first field of the @samp{info threads} display, with or
> +without an inferior qualifier (e.g., @samp{2.1} or @samp{1}).

An example always helps, so this is even better.

> Thank you very much for the review and suggestions thus far.
> Here's an updated docs patch.  Let me know how it looks.

LGTM, thanks.


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH] thread ID ranges (Per-inferior thread IDs)
  2015-12-18 18:11       ` Pedro Alves
  2015-12-18 19:36         ` Eli Zaretskii
@ 2015-12-24 14:56         ` Pedro Alves
  2015-12-24 16:24           ` Eli Zaretskii
  1 sibling, 1 reply; 10+ messages in thread
From: Pedro Alves @ 2015-12-24 14:56 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

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

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

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

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

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

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

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



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] thread ID ranges (Per-inferior thread IDs)
  2015-12-24 14:56         ` [PATCH] thread ID ranges (Per-inferior thread IDs) Pedro Alves
@ 2015-12-24 16:24           ` Eli Zaretskii
  0 siblings, 0 replies; 10+ messages in thread
From: Eli Zaretskii @ 2015-12-24 16:24 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

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

Thanks, the documentation part looks good to me.


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2015-12-24 16:24 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-12-15 19:05 [PATCH] Per-inferior thread IDs Pedro Alves
2015-12-15 19:39 ` Eli Zaretskii
2015-12-17 13:17   ` Pedro Alves
2015-12-17 16:54     ` Eli Zaretskii
2015-12-18 18:11       ` Pedro Alves
2015-12-18 19:36         ` Eli Zaretskii
2015-12-24 14:56         ` [PATCH] thread ID ranges (Per-inferior thread IDs) Pedro Alves
2015-12-24 16:24           ` Eli Zaretskii
2015-12-18 17:53 ` [PATCH] Per-inferior thread IDs Joel Brobecker
2015-12-18 18:47   ` Pedro Alves

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox