This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[MI] core awareness


The attached patch implements the core awareness, as discussed at:

	http://thread.gmane.org/gmane.comp.gdb.devel/27468/

The most current spec can be found at:

	http://article.gmane.org/gmane.comp.gdb.devel/27548

To summarize, with this patch, when debugging against gdbserver:

* The -list-thread-groups command will report the cores that each
process runs on (with or without --available)
* It's possible to pass several thread groups to -list-thread-groups
* The *stopped output includes core number

For native debugging on Linux, roughly the same is also possible.

This is mostly MI change, but includes some remote protocol tweaks,
and documentation. I'd appreciate review of those parts.

- Volodya
commit 533abb7a66898cde158ea5556604ea20d56f2a47
Author: Vladimir Prus <vladimir@codesourcery.com>
Date:   Wed Dec 16 23:39:19 2009 +0300

    	Implement core awareness.
    
    	gdb/
            * bcache.c (compare_ints): Remove
    	(print_percentage): Use compare_positive_ints.
    
    	* defs.h (compare_positive_ints): Declare.
    	* linux-nat.c (linux_nat_core_of_thread): New.
    	(linux_nat_add_target): Register the above.
    	* osdata.c (osdata_parsing_data): New fields
    	top_osdata and current_item.
    	(osdata_start_threads, osdata_end_threads): New.
    	(osdata_start_osdata): Init top_osdata.
    	(osdata_start_item): Record current item.
    	(osdata_children): New.
    	(item_children): Permit 'threads' child.
    	* osdata.h (osdata_item): New field 'children'.
    	* remote.c (struct private_thread_info): New.
    	(PACKET_qXfer_threads, use_osdata_threads): New.
    	(struct thread_item, threads_parsing_context
    	(start_thread, end_thread, thread_attributes)
    	(thread_children, threads_children, threads_elements): New.
    	(remote_threads_info): Try qXfer:threads before anything
    	else.
    	(remote_protocol_packets): Register qXfer:threads.
    	(remote_open_1): Init use_osdata_threads.
    	(struct stop_reply): New field 'core'.
    	(remote_parse_stop_reply): Parse core number.
    	(process_stop_reply): Record core number.
    	(remote_xfer_partial): Handle qXfer:threads.
    	(remote_core_of_thread): New.
    	(init_remote_ops): Register remote_core_of_thread.
    	(_initialize_remote): Register qXfer:read.
    	* target.c (target_core_of_thread): New
    	* target.h (enum target_object): New value TARGET_OBJECT_THREADS.
    	(struct target_ops): New field to_core_of_threads.
    	(target_core_of_thread): Declare.
    	* thread.c (print_thread_info): Report the core.
    	* ui-out.c (MAX_UI_OUT_LEVELS): Increase.
    	* utils.c (compare_positive_ints): New.
    	* features/osdata.dtd: Allow nested threads.
    	* features/threads.dtd: New.
    	* mi/mi-interp.c (mi_on_normal_stop): Report the core.
    	* mi/mi-main.c (struct collect_cores_data, collect_cores)
    	(print_one_inferior_data): New.
    	(print_one_inferior_data): Implementing printing of selected
    	inferiors.  Collect and print cores.
    	(output_cores): New.
    	(mi_cmd_list_thread_groups): Support --recurse. Permit specifying
    	thread groups together with --available.
    
    	gdbserver/
    	* linux-low.c (linux_core_of_thread): New.
    	(compare_ints, list_threads): New.
    	(linux_qxfer_osdata): Report threads and cores.
    	(linux_target_op): Register linux_core_of_thread.
    	* remote-utils.c (prepare_resume_reply): Report the core.
    	* server.c (handle_threads_qxfer_proper, handle_threads_qxfer):
    	New.
    	(handle_query): Handle qXfer:threads.  Announce availability
    	thereof.
    	* target.h (struct target_ops): New field core_for_threads.
    
    	gdb/doc
    	* gdb.texinfo (GDB/MI Thread Information): New.
    	(GDB/MI Async Records): Document the core field in *stopped.
    	(GDB/MI Miscellaneous Commands): Expand -list-thread-groups
    	documentation
    	(Process list): Document that osdata document may contain
    	threads.
    	(Remote Serial Protocol): Document qXfer:threads.

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 348d853..882be85 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -444,7 +444,8 @@ RUNTESTFLAGS=
 
 # XML files to build in to GDB.
 XMLFILES = $(srcdir)/features/gdb-target.dtd $(srcdir)/features/xinclude.dtd \
-	$(srcdir)/features/library-list.dtd $(srcdir)/features/osdata.dtd
+	$(srcdir)/features/library-list.dtd $(srcdir)/features/osdata.dtd \
+	$(srcdir)/features/threads.dtd
 
 # This is ser-unix.o for any system which supports a v7/BSD/SYSV/POSIX
 # interface to the serial port.  Hopefully if get ported to OS/2, VMS,
diff --git a/gdb/bcache.c b/gdb/bcache.c
index d56df07..e467c5b 100644
--- a/gdb/bcache.c
+++ b/gdb/bcache.c
@@ -301,15 +301,6 @@ bcache_xfree (struct bcache *bcache)
 
 /* Printing statistics.  */
 
-static int
-compare_ints (const void *ap, const void *bp)
-{
-  /* Because we know we're comparing two ints which are positive,
-     there's no danger of overflow here.  */
-  return * (int *) ap - * (int *) bp;
-}
-
-
 static void
 print_percentage (int portion, int total)
 {
@@ -367,9 +358,9 @@ print_bcache_statistics (struct bcache *c, char *type)
 
     /* To compute the median, we need the set of chain lengths sorted.  */
     qsort (chain_length, c->num_buckets, sizeof (chain_length[0]),
-	   compare_ints);
+	   compare_positive_ints);
     qsort (entry_size, c->unique_count, sizeof (entry_size[0]),
-	   compare_ints);
+	   compare_positive_ints);
 
     if (c->num_buckets > 0)
       {
diff --git a/gdb/defs.h b/gdb/defs.h
index b944ffb..396e9a8 100644
--- a/gdb/defs.h
+++ b/gdb/defs.h
@@ -417,6 +417,8 @@ char *ldirname (const char *filename);
 
 char **gdb_buildargv (const char *);
 
+int compare_positive_ints (const void *ap, const void *bp);
+
 /* From demangle.c */
 
 extern void set_demangling_style (char *);
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index e880838..d1d5075 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -15402,6 +15402,10 @@ are:
 @tab @code{qXfer:siginfo:write}
 @tab @code{set $_siginfo}
 
+@item @code{threads}
+@tab @code{qXfer:threads:read}
+@tab @code{info threads}
+
 @item @code{get-thread-local-@*storage-address}
 @tab @code{qGetTLSAddr}
 @tab Displaying @code{__thread} variables
@@ -21688,6 +21692,7 @@ follow development on @email{gdb@@sourceware.org} and
 * GDB/MI Stream Records::
 * GDB/MI Async Records::
 * GDB/MI Frame Information::
+* GDB/MI Thread Information::
 @end menu
 
 @node GDB/MI Result Records
@@ -21780,7 +21785,7 @@ 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}"
+@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
 following values:
 
@@ -21820,7 +21825,9 @@ If all threads are stopped, the @var{stopped} field will have the
 value of @code{"all"}.  Otherwise, the value of the @var{stopped}
 field will be a list of thread identifiers.  Presently, this list will
 always include a single thread, but frontend should be prepared to see
-several threads in the list.
+several threads in the list.  The @var{core} field reports on which
+processor core the stop event has happened.  This field may be absent
+if such information is not available.
 
 @item =thread-group-created,id="@var{id}"
 @itemx =thread-group-exited,id="@var{id}"
@@ -21897,6 +21904,34 @@ corresponds to the frame's code address.  This field may be absent.
 
 @end table
 
+@node GDB/MI Thread Information
+@subsection @sc{gdb/mi} Thread Information
+
+Whenver @value{GDBN} has to report an information about a thread, it
+uses a tuple with the following fields:
+
+@table @code
+@item id
+The numeric id assigned to the thread by @value{GDBN}. This field is
+always present.
+
+@item target-id
+Target-specific string identifying the thread.  This field is always present.
+
+@item details
+Additional information about the thread provided by the target.
+It is supposed to be human-readable and not interpreted by the
+frontend. This field is optional.
+
+@item state
+Either @samp{stopped} or @samp{running}, depending on whether the
+thread is presently running.  This field is always present.
+
+@item cores
+The value of this field is an integer number of the processor core the
+thread was last seen on.  This field is optional.
+@end table
+
 
 @c %%%%%%%%%%%%%%%%%%%%%%%%%%%% SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 @node GDB/MI Simple Examples
@@ -26202,20 +26237,83 @@ while the target is running.
 @subheading Synopsis
 
 @smallexample
--list-thread-groups [ --available ] [ @var{group} ]
+-list-thread-groups [ --available ] [ --recurse 1 ] [ @var{group} ... ]
 @end smallexample
 
-When used without the @var{group} parameter, lists top-level thread
-groups that are being debugged.  When used with the @var{group}
-parameter, the children of the specified group are listed.  The
-children can be either threads, or other groups.  At present,
-@value{GDBN} will not report both threads and groups as children at
-the same time, but it may change in future.
+Lists thread groups (@pxref{Thread groups}). When a single thread
+group is passed as the argument, lists the children specified one.
+When several thread group are passed, lists information about those
+thread groups.  Without any parameters, lists information about all
+top-level thread groups.
+
+Normally, thread groups that are being debugged are reported.
+With the @samp{--available} option, @value{GDBN} reports thread groups
+available on the target.
+
+The output of this command may have either a @samp{threads} result or
+a @samp{groups} result. The @samp{thread} result has a list of tuples
+as value, with each tuple describing a thread (@pxref{GDB/MI Thread
+Information}). The @samp{groups} result has a list of tuples as value,
+each tuple describing a thread group.  If top-level groups are
+requested (that is, no parameter is passed), or when several groups
+are passed, the output always have a @samp{groups} result. The format
+of the @samp{group} result is described below.
+
+To reduce the number of roundtrips it's possible to list thread groups
+together with their children, by passing the @samp{--recurse} option
+and the recursion depth. Presently, only recursion depth of 1 is
+permitted. If this option is present, then every reported thread group
+will also include its children, either as @samp{group} or
+@samp{threads} field.
+
+In general, any combination of option and parameters is permitted, with
+the following caveats:
+
+@itemize @bullet
+@item 
+When a single thread group is passed, the output will typically
+be the @samp{threads} result.  Because threads may not contain
+anything, the @samp{recurse} option will be ignored.
+
+@item
+When the @samp{--available} option is passed, limited information may
+be available.  In particular, the list of threads of a process might
+be inaccessible.  Further, specifying specific thread groups might
+not give any performance advantage over listing all thread groups.
+The frontend should assume that @samp{-list-thread-groups --available}
+is always an expensive operation and cache the results.
+
+@end itemize
+
+The @samp{groups} result is a list of tuples, where each tuple may
+have the following fields:
+
+@table @code
+@item id
+Identifier of the thread group.  This field is always present.
+
+@item type
+The type of the thread group. At present, only @samp{process} is a
+valid type.
+
+@item pid
+The target-specific process identifier.  This field is only present
+for thread groups of type @samp{process}.
+
+@item num_children
+The number of children this thread group has.  This field may be
+absent for an available thread group.
+
+@item threads
+This field has a list of tuples as value, each tuple describing a
+thread.  It may be present 
 
-With the @samp{--available} option, instead of reporting groups that
-are been debugged, GDB will report all thread groups available on the
-target.  Using the @samp{--available} option together with @var{group}
-is not allowed.
+@item cores
+This field is a list of integers, each identifying a core that one
+thread of the group is running on.  This field may be absent if
+such information is not available.
+
+@end table
 
 @subheading Example
 
@@ -26229,6 +26327,16 @@ is not allowed.
 @{id="1",target-id="Thread 0xb7e156b0 (LWP 21254)",
    frame=@{level="0",addr="0x0804891f",func="foo",args=[@{name="i",value="10"@}],
            file="/tmp/a.c",fullname="/tmp/a.c",line="158"@},state="running"@}]]
+-list-thread-groups --available
+^done,groups=[@{id="17",type="process",pid="yyy",num_children="2",cores=[1,2]@}]
+-list-thread-groups --available --recurse 1
+ ^done,groups=[@{id="17", types="process",pid="yyy",num_children="2",cores=[1,2],
+                threads=[@{id="1",target-id="Thread 0xb7e14b90",cores=[1]@},
+                         @{id="2",target-id="Thread 0xb7e14b90",cores=[2]@}]@},..]
+-list-thread-groups --available --recurse 1 17 18
+^done,groups=[@{id="17", types="process",pid="yyy",num_children="2",cores=[1,2],
+               threads=[@{id="1",target-id="Thread 0xb7e14b90",cores=[1]@},
+                        @{id="2",target-id="Thread 0xb7e14b90",cores=[2]@}]@},...]
 @end smallexample
 
 @subheading The @code{-interpreter-exec} Command
@@ -27978,6 +28086,7 @@ Show the current setting of the target wait timeout.
 * File-I/O Remote Protocol Extension::
 * Library List Format::
 * Memory Map Format::
+* Thread List Format::
 @end menu
 
 @node Overview
@@ -28878,6 +28987,10 @@ If @var{n} is @samp{thread}, then @var{r} is the @var{thread-id} of
 the stopped thread, as specified in @ref{thread-id syntax}.
 
 @item
+If @var{n} is @samp{core}, then @var{r} is the hexadecimal number of
+the core on which the stop event was detected.
+
+@item
 If @var{n} is a recognized @dfn{stop reason}, it describes a more
 specific event that stopped the target.  The currently defined stop
 reasons are listed below.  @var{aa} should be @samp{05}, the trap
@@ -28911,8 +29024,6 @@ logged execution events, because it has reached the end (or the
 beginning when executing backward) of the log.  The value of @var{r}
 will be either @samp{begin} or @samp{end}.  @xref{Reverse Execution}, 
 for more information.
-
-
 @end table
 
 @item W @var{AA}
@@ -29452,6 +29563,12 @@ These are the currently defined stub features and their properties:
 @tab @samp{-}
 @tab Yes
 
+@item @samp{qXfer:threads:read}
+@tab No
+@tab @samp{-}
+@tab Yes
+
+
 @item @samp{QNonStop}
 @tab No
 @tab @samp{-}
@@ -29535,6 +29652,10 @@ The remote stub understands the @samp{qXfer:siginfo:read} packet
 The remote stub understands the @samp{qXfer:siginfo:write} packet
 (@pxref{qXfer siginfo write}).
 
+@item qXfer:threads:read
+The remote stub understands the @samp{qXfer:threads:read} packet
+(@pxref{qXfer threads read}).
+
 @item QNonStop
 The remote stub understands the @samp{QNonStop} packet
 (@pxref{QNonStop}).
@@ -29727,6 +29848,15 @@ This packet is not probed by default; the remote stub must request it,
 by supplying an appropriate @samp{qSupported} response
 (@pxref{qSupported}).
 
+@item qXfer:threads:read::@var{offset},@var{length}
+@anchor{qXfer threads read}
+Access the list of threads on target.  @xref{Thread List Format}.  The
+annex part of the generic @samp{qXfer} packet must be empty
+(@pxref{qXfer read}).
+
+This packet is not probed by default; the remote stub must request it,
+by supplying an appropriate @samp{qSupported} response (@pxref{qSupported}).
+
 @item qXfer:osdata:read::@var{offset},@var{length}
 @anchor{qXfer osdata read}
 Access the target's @dfn{operating system information}.  
@@ -31710,6 +31840,32 @@ The formal DTD for memory map format is given below:
 <!ATTLIST property      name    CDATA   #REQUIRED>
 @end smallexample
 
+@node Thread List Format
+@section Thread List Format
+@cindex thread list format
+
+To efficiently update the list of threads and their attributes,
+@value{GDBN} gdb requests, using the @samp{qXfer:threads:read}
+package (@pxref{qXfer threads read}) and XML document with the
+format described below.
+
+The top-level structure of the document is shown below:
+
+@smallexample
+<?xml version="1.0"?>
+<threads>
+    <thread id="id" core="0">
+    some details
+    </thread>     
+</memory-map>
+@end smallexample
+
+Each @samp{thread} element must have the @samp{id} attribute that
+identifies the thread (@pxref{thread-id syntax}). The
+@samp{core} attribute, if present, specifies which process core
+the thread was last executing on.  The content of the of @samp{thread}
+element is interpreted as hunam-readable auxilliary information.
+
 @include agentexpr.texi
 
 @node Target Descriptions
@@ -32269,15 +32425,34 @@ An example document is:
     <column name="pid">1</column>
     <column name="user">root</column>
     <column name="command">/sbin/init</column>
+    <column name="cores">1,2,3</column>
+    <threads>
+      <item>
+      <column name="tid">12</column>
+      <column name="core">3</column>
+      </item>
+    </threads>
   </item>
 </osdata>
 @end smallexample
 
-Each item should include a column whose name is @samp{pid}.  The value
+The document has an @samp{osdata} element that contains a number of
+@samp{item} elements describing processes. Each process @samp{item}
+may contain @samp{threads} element with items describing threads of
+that process. Both process and thread items contain a number of
+@samp{column} elements with various information.
+
+Each process item should include a column whose name is @samp{pid}.  The value
 of that column should identify the process on the target.  The
 @samp{user} and @samp{command} columns are optional, and will be
-displayed by @value{GDBN}.  Target may provide additional columns,
-which @value{GDBN} currently ignores.
+displayed by @value{GDBN}.  The @samp{cores} column, if present,
+should contain a comma-separated list of cores that this process
+is running on.
+
+Each thread item should include a column whose name is @samp{tid}.
+The value of that is a thread identifier, specific to the operating
+system. The @samp{core} column, if present, is the integer number of
+the core the thread is running on.
 
 @include gpl.texi
 
diff --git a/gdb/features/osdata.dtd b/gdb/features/osdata.dtd
index a04506b..67de837 100644
--- a/gdb/features/osdata.dtd
+++ b/gdb/features/osdata.dtd
@@ -10,7 +10,10 @@
 <!ATTLIST osdata version CDATA #FIXED "1.0">
 <!ATTLIST osdata type CDATA #REQUIRED>
 
-<!ELEMENT item (column*)>
+<!ELEMENT item (column*, threads?)>
 
 <!ELEMENT column (#PCDATA)>
 <!ATTLIST column name CDATA #REQUIRED>
+
+<!ELEMENT threads (item*)>
+
diff --git a/gdb/features/threads.dtd b/gdb/features/threads.dtd
new file mode 100644
index 0000000..e7c3a11
--- /dev/null
+++ b/gdb/features/threads.dtd
@@ -0,0 +1,14 @@
+<!-- Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+
+     Copying and distribution of this file, with or without modification,
+     are permitted in any medium without royalty provided the copyright
+     notice and this notice are preserved.  -->
+
+<!ELEMENT threads (thread*)>
+<!ATTLIST threads version CDATA #FIXED "1.0">
+
+<!ELEMENT thread (#PCDATA)>
+
+<!ATTLIST thread id CDATA #REQUIRED>
+<!ATTLIST thread core CDATA #IMPLIED>
+
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 45dd9f7..d1d853d 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -137,6 +137,7 @@ static void *add_lwp (ptid_t ptid);
 static int my_waitpid (int pid, int *status, int flags);
 static int linux_stopped_by_watchpoint (void);
 static void mark_lwp_dead (struct lwp_info *lwp, int wstat);
+static int linux_core_of_thread (ptid_t ptid);
 
 struct pending_signals
 {
@@ -2789,6 +2790,111 @@ linux_read_offsets (CORE_ADDR *text_p, CORE_ADDR *data_p)
 #endif
 
 static int
+compare_ints (const void *xa, const void *xb)
+{
+  int a = *(const int *)xa;
+  int b = *(const int *)xb;
+
+  return a - b;
+}
+
+/* Given PID, print information about every thread in that process to BUFFER.
+   Set *CORES to a string with a comma-separated list of cores that threads
+   of PID execute on.  The string must be freed by the caller.  BUFFER must
+   be initialized. If no cores are found, *CORES will be set to NULL. */
+static void
+list_threads (int pid, struct buffer *buffer, char **cores)
+{
+  int count = 0;
+  int allocated = 10;
+  int *core_numbers = xmalloc (sizeof (int) * allocated);
+  char pathname[128];
+  DIR *dir;
+  struct dirent *dp;
+  struct stat statbuf;
+
+  sprintf (pathname, "/proc/%d/task", pid);
+  if (stat (pathname, &statbuf) == 0 && S_ISDIR (statbuf.st_mode))
+    {
+      dir = opendir (pathname);
+      if (!dir)
+	return;
+
+      buffer_xml_printf (buffer, "<threads>");
+      while ((dp = readdir (dir)) != NULL)
+	{
+	  unsigned long lwp = strtoul (dp->d_name, NULL, 10);
+	  
+	  if (lwp != 0)
+	    {
+	      unsigned core = linux_core_of_thread (ptid_build (pid, lwp, 0));
+
+	      if (core != -1)
+		{
+		  char s[11];
+		  sprintf (s, "%u", core);
+
+		  if (count == allocated) 
+		    core_numbers = realloc (core_numbers, 
+					    sizeof (int ) * (allocated *= 2));
+		  core_numbers[count++] = core;
+		  buffer_xml_printf (buffer,
+				     "<item>"
+				     "<column name=\"tid\">%s</column>"
+				     "<column name=\"core\">%s</column>"
+				     "</item>", dp->d_name, s);
+		}
+	      else
+		{
+		  buffer_xml_printf (buffer,
+				     "<item>"
+				     "<column name=\"tid\">%s</column>"
+				     "</item>", dp->d_name);
+		}
+	    }	  
+	}
+      buffer_xml_printf (buffer, "</threads>");
+    }
+
+  *cores = NULL;
+  if (count > 0)
+    {
+      struct buffer buffer2;
+      int *b;
+      int *e;
+      qsort (core_numbers, count, sizeof (int), compare_ints);
+
+      /* Remove duplicates. */
+      b = core_numbers;
+      e = core_numbers + count;
+
+      while (b != e) {
+	if ((b + 1 < e) && (*b == *(b+1)))
+	  {
+	    *(b + 1) = *(e - 1);
+	    --e;	    
+	  }
+	else
+	  {
+	    ++b;
+	  }	  
+      }
+
+      buffer_init (&buffer2);
+
+      for (b = core_numbers; b != e; ++b) {
+	char number[11];
+	sprintf (number, "%u", *b);
+	buffer_xml_printf (&buffer2, "%s%s", 
+			   (b == core_numbers) ? "" : ",", number);
+      }
+      buffer_grow_str0 (&buffer2, "");
+      
+      *cores = buffer_finish (&buffer2);
+    }  
+}
+
+static int
 linux_qxfer_osdata (const char *annex,
 		    unsigned char *readbuf, unsigned const char *writebuf,
 		    CORE_ADDR offset, int len)
@@ -2837,15 +2943,20 @@ linux_qxfer_osdata (const char *annex,
 		 FILE *f;
 		 char cmd[MAXPATHLEN + 1];
 		 struct passwd *entry;
+		 int pid;
 
 		 sprintf (pathname, "/proc/%s/cmdline", dp->d_name);
 		 entry = getpwuid (statbuf.st_uid);
 
+		 pid = (int)strtoul (dp->d_name, NULL, 10);
+
 		 if ((f = fopen (pathname, "r")) != NULL)
 		   {
 		     size_t len = fread (cmd, 1, sizeof (cmd) - 1, f);
 		     if (len > 0)
 		       {
+			 struct buffer thread_buffer;
+			 char *cores = 0;	 
 			 int i;
 			 for (i = 0; i < len; i++)
 			   if (cmd[i] == '\0')
@@ -2857,11 +2968,29 @@ linux_qxfer_osdata (const char *annex,
 			   "<item>"
 			   "<column name=\"pid\">%s</column>"
 			   "<column name=\"user\">%s</column>"
-			   "<column name=\"command\">%s</column>"
-			   "</item>",
+			   "<column name=\"command\">%s</column>",
 			   dp->d_name,
 			   entry ? entry->pw_name : "?",
 			   cmd);
+
+			 buffer_init (&thread_buffer);
+			 list_threads (pid, &thread_buffer, &cores);
+
+			 if (cores)
+			   {
+			     buffer_xml_printf (
+			         &buffer, 
+				 "<column name=\"cores\">%s</column>", cores);
+			     free (cores);
+			   }
+			
+			 buffer_grow (&buffer, 
+				      thread_buffer.buffer, 
+				      thread_buffer.used_size);
+			 free (buffer_finish (&thread_buffer));
+
+			 
+			 buffer_xml_printf (&buffer, "</item>");
 		       }
 		     fclose (f);
 		   }
@@ -3139,6 +3268,53 @@ linux_qxfer_spu (const char *annex, unsigned char *readbuf,
   return ret;
 }
 
+static int
+linux_core_of_thread (ptid_t ptid)
+{
+  char filename[sizeof ("/proc//task//stat") 
+		 + 2*20 /* decimal digits for 2 numbers, max 2^64 bit each */
+		 + 1];
+  FILE *f;
+  char *content = NULL;
+  char *p;
+  char *ts = 0;
+  int content_read = 0;
+  int i;
+  int core;
+  
+  sprintf (filename, "/proc/%d/task/%ld/stat", 
+	   ptid_get_pid (ptid), ptid_get_lwp (ptid));
+  f = fopen (filename, "r");
+
+  for (;;)
+    {
+      int n;
+      content = realloc (content, content_read + 1024);
+      n = fread (content + content_read, 1, 1024, f);
+      content_read += n;
+      if (n < 1024)
+	{
+	  content[content_read] = '\0';
+	  break;
+	}	      
+    }
+
+  p = strchr (content, '(');
+  p = strchr (p, ')') + 2; /* skip ")" and a whitespace. */
+    
+  p = strtok_r (p, " ", &ts);
+  for (i = 0; i != 36; ++i)
+    p = strtok_r (NULL, " ", &ts);
+
+  if (sscanf (p, "%d", &core) == 0)
+    core = -1;
+  
+  free (content);
+  fclose (f);
+
+  return core;
+}
+
 static struct target_ops linux_target_ops = {
   linux_create_inferior,
   linux_attach,
@@ -3178,10 +3354,11 @@ static struct target_ops linux_target_ops = {
   linux_start_non_stop,
   linux_supports_multi_process,
 #ifdef USE_THREAD_DB
-  thread_db_handle_monitor_command
+  thread_db_handle_monitor_command,
 #else
-  NULL
+  NULL,
 #endif
+  linux_core_of_thread
 };
 
 static void
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index 158b653..97c076d 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1170,6 +1170,7 @@ prepare_resume_reply (char *buf, ptid_t ptid,
 	       gdbserver to know what inferior_ptid is.  */
 	    if (1 || !ptid_equal (general_thread, ptid))
 	      {
+		int core = -1;
 		/* In non-stop, don't change the general thread behind
 		   GDB's back.  */
 		if (!non_stop)
@@ -1179,6 +1180,17 @@ prepare_resume_reply (char *buf, ptid_t ptid,
 		buf = write_ptid (buf, ptid);
 		strcat (buf, ";");
 		buf += strlen (buf);
+
+		if (the_target->core_for_thread)
+		  core = (*the_target->core_for_thread) (ptid);
+		if (core != -1)
+		  {
+		    sprintf (buf, "core:");
+		    buf += strlen (buf);
+		    sprintf (buf, "%x", core);
+		    strcat (buf, ";");
+		    buf += strlen (buf);
+		  }
 	      }
 	  }
 
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 14bc7e7..62e3e3d 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -707,6 +707,87 @@ handle_monitor_command (char *mon)
     }
 }
 
+static void
+handle_threads_qxfer_proper (struct buffer *buffer)
+{
+  struct inferior_list_entry *thread;
+
+  buffer_grow_str (buffer, "<threads>\n");
+
+  for (thread = all_threads.head; thread; thread = thread->next)
+    {
+      ptid_t ptid = thread_to_gdb_id ((struct thread_info *)thread);
+      char ptid_s[100];
+      int core = -1;
+      char core_s[21];
+
+      write_ptid (ptid_s, ptid);
+
+      if (the_target->core_for_thread)
+	core = (*the_target->core_for_thread) (ptid);
+
+      if (core != -1) 
+	{
+	  sprintf (core_s, "%d", core);
+	  buffer_xml_printf (buffer, "<thread id=\"%s\" core=\"%s\"/>\n", 
+			     ptid_s, core_s);
+	}
+      else
+	{
+	  buffer_xml_printf (buffer, "<thread id=\"%s\"/>\n",  
+			     ptid_s);
+	}
+    }
+
+  buffer_grow_str0 (buffer, "</threads>\n");
+}
+
+static int
+handle_threads_qxfer (const char *annex,
+		      unsigned char *readbuf,
+		      CORE_ADDR offset, int length)
+{
+  static char *result = 0;
+  static unsigned int result_length = 0;
+
+  if (annex && strcmp (annex, "") != 0)
+    return 0;
+
+  if (offset == 0)
+    {
+      struct buffer buffer;
+      /* When asked for data at offset 0, generate everything and store into
+	 'result'. Successive reads will be served off 'result'.  */
+      if (result)
+	free (result);
+      
+      buffer_init (&buffer);
+
+      handle_threads_qxfer_proper (&buffer);
+      
+      result = buffer_finish (&buffer);
+      result_length = strlen (result);
+      buffer_free (&buffer);	  
+    }
+  
+  if (offset >= result_length)
+    {
+      /* We're out of data.  */
+      free (result);
+      result = NULL;
+      result_length = 0;
+      return 0;
+    }
+  
+  if (length > result_length - offset)
+    length = result_length - offset;
+  
+  memcpy (readbuf, result + offset, length);
+
+  return length;
+
+}
+
 /* Handle all of the extended 'q' packets.  */
 void
 handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
@@ -1112,6 +1193,43 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
       return;
     }
 
+  if (strncmp ("qXfer:threads:read:", own_buf, 19) == 0)
+    {
+      unsigned char *data;
+      int n;
+      CORE_ADDR ofs;
+      unsigned int len;
+      char *annex;
+
+      require_running (own_buf);
+
+      /* Reject any annex; grab the offset and length.  */
+      if (decode_xfer_read (own_buf + 19, &annex, &ofs, &len) < 0
+	  || annex[0] != '\0')
+	{
+	  strcpy (own_buf, "E00");
+	  return;
+	}
+
+      /* Read one extra byte, as an indicator of whether there is
+	 more.  */
+      if (len > PBUFSIZ - 2)
+	len = PBUFSIZ - 2;
+      data = malloc (len + 1);
+      if (!data)
+	return;
+      n = handle_threads_qxfer (annex, data, ofs, len + 1);
+      if (n < 0)
+	write_enn (own_buf);
+      else if (n > len)
+	*new_packet_len_p = write_qxfer_response (own_buf, data, len, 1);
+      else
+	*new_packet_len_p = write_qxfer_response (own_buf, data, n, 0);
+
+      free (data);
+      return;     
+    }
+
   /* Protocol features query.  */
   if (strncmp ("qSupported", own_buf, 10) == 0
       && (own_buf[10] == ':' || own_buf[10] == '\0'))
@@ -1168,6 +1286,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
       if (target_supports_non_stop ())
 	strcat (own_buf, ";QNonStop+");
 
+      strcat (own_buf, ";qXfer:threads:read+");
+
       return;
     }
 
diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
index 0e4b127..8e8778a 100644
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -283,6 +283,9 @@ struct target_ops
   /* If not NULL, target-specific routine to process monitor command.
      Returns 1 if handled, or 0 to perform default processing.  */
   int (*handle_monitor_command) (char *);
+
+  /* Returns the core given a thread, or -1 if not known.  */
+  int (*core_for_thread) (ptid_t);
 };
 
 extern struct target_ops *the_target;
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 2f388fb..bd0044b 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -346,4 +346,6 @@ extern struct cleanup *make_cleanup_restore_current_thread (void);
    INFERIOR_PTID.  INFERIOR_PTID *must* be in the thread list.  */
 extern struct thread_info* inferior_thread (void);
 
+extern void update_thread_list (void);
+
 #endif /* GDBTHREAD_H */
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index c0afecd..b232caf 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -5421,6 +5421,59 @@ linux_nat_thread_address_space (struct target_ops *t, ptid_t ptid)
   return inf->aspace;
 }
 
+/* Return the core for a thread.  */
+static int
+linux_nat_core_of_thread (struct target_ops *ops, ptid_t ptid)
+{
+  struct cleanup *back_to;
+  char *filename;
+  FILE *f;
+  char *content = NULL;
+  char *p;
+  char *ts = 0;
+  int content_read = 0;
+  int i;
+  int core;
+
+  filename = xstrprintf ("/proc/%d/task/%ld/stat",
+			 GET_PID (ptid), GET_LWP (ptid));
+  back_to = make_cleanup (xfree, filename);
+
+  f = fopen (filename, "r");
+  make_cleanup_fclose (f);
+
+  for (;;)
+    {
+      int n;
+      content = xrealloc (content, content_read + 1024);
+      n = fread (content + content_read, 1, 1024, f);
+      content_read += n;
+      if (n < 1024)
+	{
+	  content[content_read] = '\0';
+	  break;
+	}
+    }
+
+  make_cleanup (xfree, content);
+
+  p = strchr (content, '(');
+  p = strchr (p, ')') + 2; /* skip ")" and a whitespace. */
+
+  /* If the first field after program name has index 0, then core number is
+     the field with index 36. There's no constant for that anywhere.  */
+  p = strtok_r (p, " ", &ts);
+  for (i = 0; i != 36; ++i)
+    p = strtok_r (NULL, " ", &ts);
+
+  if (sscanf (p, "%d", &core) == 0)
+    core = -1;
+
+  do_cleanups (back_to);
+
+  return core;
+}
+
 void
 linux_nat_add_target (struct target_ops *t)
 {
@@ -5461,6 +5514,8 @@ linux_nat_add_target (struct target_ops *t)
 
   t->to_supports_multi_process = linux_nat_supports_multi_process;
 
+  t->to_core_of_thread = linux_nat_core_of_thread;
+
   /* We don't change the stratum; this target will sit at
      process_stratum and thread_db will set at thread_stratum.  This
      is a little strange, since this is a multi-threaded-capable
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index 248cd66..af51bde 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -339,6 +339,7 @@ mi_on_normal_stop (struct bpstats *bs, int print_frame)
 
   if (print_frame)
     {
+      int core;
       if (uiout != mi_uiout)
 	{
 	  /* The normal_stop function has printed frame information into 
@@ -364,6 +365,10 @@ mi_on_normal_stop (struct bpstats *bs, int print_frame)
 	}
       else
 	ui_out_field_string (mi_uiout, "stopped-threads", "all");
+
+      core = target_core_of_thread (inferior_ptid);
+      if (core != -1)
+	ui_out_field_int (mi_uiout, "core", core);
     }
   
   fputs_unfiltered ("*stopped", raw_stdout);
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 2332752..b152d98 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -359,11 +359,44 @@ mi_cmd_thread_info (char *command, char **argv, int argc)
   print_thread_info (uiout, thread, -1);
 }
 
+struct collect_cores_data
+{
+  int pid;
+
+  VEC (int) *cores;
+};
+
+static int collect_cores (struct thread_info *ti, void *xdata)
+{
+  struct collect_cores_data *data = xdata;
+
+  if (ptid_get_pid (ti->ptid) == data->pid)
+    {
+      int core = target_core_of_thread (ti->ptid);
+      if (core != -1)
+	VEC_safe_push (int, data->cores, core);
+    }
+
+  return 0;
+}
+
+struct print_one_inferior_data
+{
+  int recurse;
+  VEC (int) *inferiors;
+};
+
 static int
-print_one_inferior (struct inferior *inferior, void *arg)
+print_one_inferior (struct inferior *inferior, void *xdata)
 {
-  if (inferior->pid != 0)
+  struct print_one_inferior_data *top_data = xdata;
+  
+  if (VEC_length (int, top_data->inferiors) == 0
+      || bsearch (&(inferior->pid), VEC_address (int, top_data->inferiors),
+		  VEC_length (int, top_data->inferiors), sizeof (int),
+		  compare_positive_ints))
     {
+      struct collect_cores_data data;
       struct cleanup *back_to
 	= make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
 
@@ -371,36 +404,128 @@ print_one_inferior (struct inferior *inferior, void *arg)
       ui_out_field_string (uiout, "type", "process");
       ui_out_field_int (uiout, "pid", inferior->pid);
 
+      data.pid = inferior->pid;
+      data.cores = 0;
+      iterate_over_threads (collect_cores, &data);
+
+      if (VEC_length (int, data.cores)) 
+	{
+	  int elt;
+	  int i;
+	  int *b, *e;
+	  struct cleanup *back_to_2 =
+	    make_cleanup_ui_out_list_begin_end (uiout, "cores");
+
+	  qsort (VEC_address (int, data.cores),
+		 VEC_length (int, data.cores), sizeof (int),
+		 compare_positive_ints);
+
+	  b = VEC_address (int, data.cores);
+	  e = b + VEC_length (int, data.cores);
+
+	  /* Remove duplicates.  */
+	  while (b != e) {
+	    if ((b + 1 < e) && (*b == *(b+1)))
+	      {
+		*(b + 1) = *(e - 1);
+		--e;
+	      }
+	    else
+	      {
+		++b;
+	      }
+	  }
+	  	    
+	  for (i = 0; VEC_iterate (int, data.cores, i, elt); ++i)
+	    ui_out_field_int (uiout, NULL, elt);
+
+	  do_cleanups (back_to_2);
+	}
+
+      if (top_data->recurse)
+	print_thread_info (uiout, -1, inferior->pid);    
+
       do_cleanups (back_to);
     }
 
   return 0;
 }
 
+/* Output a field named 'cores' with a list as the value. The elements of
+   the list are obtained by splitting 'cores' on comma.  */
+static void
+output_cores (struct ui_out *uiout, const char *field_name, const char *xcores)
+{
+  struct cleanup *back_to = make_cleanup_ui_out_list_begin_end (uiout, 
+								field_name);
+  char *cores = xstrdup (xcores);
+  char *p = cores;
+  char *ts = 0;
+
+  make_cleanup (xfree, cores);
+
+  for (p = strtok_r (p, ",", &ts); p;  p = strtok_r (NULL, ",", &ts))
+    ui_out_field_string (uiout, NULL, p);
+ 
+  do_cleanups (back_to);
+}
+
 void
 mi_cmd_list_thread_groups (char *command, char **argv, int argc)
 {
   struct cleanup *back_to;
   int available = 0;
-  char *id = NULL;
+  int recurse = 0;
+  VEC (int) *ids = 0;
 
-  if (argc > 0 && strcmp (argv[0], "--available") == 0)
+  enum opt
     {
-      ++argv;
-      --argc;
-      available = 1;
-    }
+      AVAILABLE_OPT, RECURSE_OPT
+    };
+  static struct mi_opt opts[] =
+  {
+    {"-available", AVAILABLE_OPT, 0},
+    {"-recurse", RECURSE_OPT, 1},
+    { 0, 0, 0 }
+  };
 
-  if (argc > 0)
-    id = argv[0];
+  int optind = 0;
+  char *optarg;
 
-  back_to = make_cleanup (null_cleanup, NULL);
+  while (1)
+    {
+      int opt = mi_getopt ("-list-thread-groups", argc, argv, opts, 
+			   &optind, &optarg);
+      if (opt < 0)
+	break;
+      switch ((enum opt) opt)
+	{
+	case AVAILABLE_OPT: 
+	  available = 1; 
+	  break;
+	case RECURSE_OPT:
+	  if (strcmp (optarg, "1") != 0)
+	    error ("only '1' is permitted value for the '--recurse' option");
+	  recurse = 1;
+	  break;
+	}
+    }
 
-  if (available && id)
+  for (; optind < argc; ++optind)
     {
-      error (_("Can only report top-level available thread groups"));
+      char *end;
+      int inf = strtoul (argv[optind], &end, 0);
+      if (*end != '\0')
+	    error ("invalid group id '%s'", argv[optind]);
+      VEC_safe_push (int, ids, inf);
     }
-  else if (available)
+  qsort (VEC_address (int, ids), 
+	 VEC_length (int, ids),
+	 sizeof (int), compare_positive_ints);
+
+  back_to = make_cleanup ((void (*)(void *))VEC_OP (int, free), &ids);
+
+  if (available)
     {
       struct osdata *data;
       struct osdata_item *item;
@@ -416,12 +541,25 @@ mi_cmd_list_thread_groups (char *command, char **argv, int argc)
 			ix_items, item);
 	   ix_items++)
 	{
-	  struct cleanup *back_to =
-	    make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
+	  struct cleanup *back_to;
 
 	  const char *pid = get_osdata_column (item, "pid");
 	  const char *cmd = get_osdata_column (item, "command");
 	  const char *user = get_osdata_column (item, "user");
+	  const char *cores = get_osdata_column (item, "cores");
+
+	  int pid_i = strtoul (pid, NULL, 0);
+
+	  /* At present, the target will return all available processes
+	     and if information about specific ones was required, we filter
+	     undesired processes here.  */
+	  if (ids && bsearch (&pid_i, VEC_address (int, ids), 
+			      VEC_length (int, ids), 
+			      sizeof (int), compare_positive_ints) == NULL)
+	      continue;
+
+
+	  back_to = make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
 
 	  ui_out_field_fmt (uiout, "id", "%s", pid);
 	  ui_out_field_string (uiout, "type", "process");
@@ -429,21 +567,57 @@ mi_cmd_list_thread_groups (char *command, char **argv, int argc)
 	    ui_out_field_string (uiout, "description", cmd);
 	  if (user)
 	    ui_out_field_string (uiout, "user", user);
+	  if (cores)
+	    output_cores (uiout, "cores", cores);
+
+	  if (item->children && recurse)
+	    {
+	      struct osdata_item *child;
+	      int ix_child;	      
+
+	      make_cleanup_ui_out_list_begin_end (uiout, "threads");
+
+	      for (ix_child = 0; VEC_iterate (osdata_item_s, 
+					      item->children->items, 
+					      ix_child, child); ++ix_child)
+		{
+		  struct cleanup *back_to_2 = 
+		    make_cleanup_ui_out_tuple_begin_end (uiout, NULL);
+
+		  const char *tid = get_osdata_column (child, "tid");
+		  const char *tcore = get_osdata_column (child, "core");
+		  ui_out_field_string (uiout, "id", tid);
+		  if (tcore)
+		    ui_out_field_string (uiout, "core", tcore);
+
+		  do_cleanups (back_to_2);
+		}	      
+	    }
 
 	  do_cleanups (back_to);
 	}
     }
-  else if (id)
+  else if (VEC_length (int, ids) == 1)
     {
-      int pid = atoi (id);
+      /* Local thread groups, single id. */
+      int pid = *VEC_address (int, ids);
       if (!in_inferior_list (pid))
-	error ("Invalid thread group id '%s'", id);
+	error ("Invalid thread group id '%d'", pid);
       print_thread_info (uiout, -1, pid);    
     }
   else
     {
+      struct print_one_inferior_data data;
+      data.recurse = recurse;
+      data.inferiors = ids;
+
+      /* Local thread groups. Either no explicit ids -- and we
+	 print everything, or several explicit ids. In both cases,
+	 we print more than one group, and have to use 'groups'
+	 as the top-level element.  */
       make_cleanup_ui_out_list_begin_end (uiout, "groups");
-      iterate_over_inferiors (print_one_inferior, NULL);
+      update_thread_list ();
+      iterate_over_inferiors (print_one_inferior, &data);
     }
   
   do_cleanups (back_to);
diff --git a/gdb/osdata.c b/gdb/osdata.c
index 3d2c23c..394fc4d 100644
--- a/gdb/osdata.c
+++ b/gdb/osdata.c
@@ -50,8 +50,10 @@ osdata_parse (const char *xml)
 /* Internal parsing data passed to all XML callbacks.  */
 struct osdata_parsing_data
   {
+    struct osdata *top_osdata;
     struct osdata *osdata;
     char *property_name;
+    struct osdata_item *current_item;
   };
 
 /* Handle the start of a <osdata> element.  */
@@ -71,9 +73,37 @@ osdata_start_osdata (struct gdb_xml_parser *parser,
   type = VEC_index (gdb_xml_value_s, attributes, 0)->value;
   osdata = XZALLOC (struct osdata);
   osdata->type = xstrdup (type);
+  data->top_osdata = data->osdata = osdata;
+}
+
+/* Handle the start of a <threads> element.  */
+
+static void
+osdata_start_threads (struct gdb_xml_parser *parser,
+		      const struct gdb_xml_element *element,
+		      void *user_data, VEC(gdb_xml_value_s) *attributes)
+{
+  struct osdata_parsing_data *data = user_data;
+  char *type;
+  struct osdata *osdata;
+
+  osdata = XZALLOC (struct osdata);
+  osdata->type = xstrdup ("threads");
   data->osdata = osdata;
 }
 
+static void
+osdata_end_threads (struct gdb_xml_parser *parser,
+		    const struct gdb_xml_element *element,
+		    void *user_data, const char *body_text)
+{
+  struct osdata_parsing_data *data = user_data;
+  
+  data->current_item->children = data->osdata;
+  data->osdata = data->top_osdata;
+}
+
+
 /* Handle the start of a <item> element.  */
 
 static void
@@ -84,6 +114,8 @@ osdata_start_item (struct gdb_xml_parser *parser,
   struct osdata_parsing_data *data = user_data;
   struct osdata_item item = { NULL };
   VEC_safe_push (osdata_item_s, data->osdata->items, &item);
+  if (data->osdata == data->top_osdata)
+    data->current_item = VEC_last (osdata_item_s, data->osdata->items);
 }
 
 /* Handle the start of a <column> element.  */
@@ -137,10 +169,15 @@ const struct gdb_xml_attribute column_attributes[] = {
   { NULL, GDB_XML_AF_NONE, NULL, NULL }
 };
 
+extern const struct gdb_xml_element osdata_children[];
+
 const struct gdb_xml_element item_children[] = {
   { "column", column_attributes, NULL,
     GDB_XML_EF_REPEATABLE | GDB_XML_EF_OPTIONAL,
     osdata_start_column, osdata_end_column },
+  { "threads", NULL, osdata_children,
+    GDB_XML_EF_OPTIONAL, osdata_start_threads,
+    osdata_end_threads },
   { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
 };
 
diff --git a/gdb/osdata.h b/gdb/osdata.h
index 4ada454..485789e 100644
--- a/gdb/osdata.h
+++ b/gdb/osdata.h
@@ -22,6 +22,9 @@
 
 #include "vec.h"
 
+typedef struct osdata *osdata_p;
+DEF_VEC_P(osdata_p);
+
 typedef struct osdata_column
 {
   char *name;
@@ -32,6 +35,13 @@ DEF_VEC_O(osdata_column_s);
 typedef struct osdata_item
 {
   VEC(osdata_column_s) *columns;
+
+  /* Nested items. Presently, a given item may only contain
+     children of specific kind, and children->type gives
+     the kind of children. In fact, only 'threads' may be
+     the kind of children right now.  */
+  osdata_p children;
+  
 } osdata_item_s;
 DEF_VEC_O(osdata_item_s);
 
@@ -41,8 +51,7 @@ struct osdata
 
   VEC(osdata_item_s) *items;
 };
-typedef struct osdata *osdata_p;
-DEF_VEC_P(osdata_p);
+
 
 struct osdata *osdata_parse (const char *xml);
 void osdata_free (struct osdata *);
diff --git a/gdb/remote.c b/gdb/remote.c
index 9fa92fb..eb47713 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -60,6 +60,7 @@
 #include "remote-fileio.h"
 #include "gdb/fileio.h"
 #include "gdb_stat.h"
+#include "xml-support.h"
 
 #include "memory-map.h"
 
@@ -303,6 +304,13 @@ struct remote_state
   int ctrlc_pending_p;
 };
 
+/* Private data that we'll store in (struct thread_info)->private.  */
+struct private_thread_info
+{
+  char *extra;
+  int core;
+};
+
 /* Returns true if the multi-process extensions are in effect.  */
 static int
 remote_multi_process_p (struct remote_state *rs)
@@ -1054,6 +1062,7 @@ enum {
   PACKET_qXfer_spu_read,
   PACKET_qXfer_spu_write,
   PACKET_qXfer_osdata,
+  PACKET_qXfer_threads,
   PACKET_qGetTLSAddr,
   PACKET_qSupported,
   PACKET_QPassSignals,
@@ -2303,6 +2312,76 @@ remote_find_new_threads (void)
 			      CRAZY_MAX_THREADS);
 }
 
+typedef struct thread_item
+{
+  ptid_t ptid;
+  char *extra;
+  int core;
+} thread_item_t;
+DEF_VEC_O(thread_item_t);
+
+struct threads_parsing_context
+{
+  VEC (thread_item_t) *items;
+};
+
+static void
+start_thread (struct gdb_xml_parser *parser,
+	      const struct gdb_xml_element *element,
+	      void *user_data, VEC(gdb_xml_value_s) *attributes)
+{
+  struct threads_parsing_context *data = user_data;
+
+  struct thread_item item;
+  char *id;
+
+  id = VEC_index (gdb_xml_value_s, attributes, 0)->value;
+  item.ptid = read_ptid (id, NULL);
+
+  if (VEC_length (gdb_xml_value_s, attributes) > 1)
+    item.core = *(ULONGEST *) VEC_index (gdb_xml_value_s, attributes, 1)->value;
+  else
+    item.core = -1;
+
+  item.extra = 0;
+
+  VEC_safe_push (thread_item_t, data->items, &item);
+}
+
+static void 
+end_thread (struct gdb_xml_parser *parser, 
+	    const struct gdb_xml_element *element,
+	    void *user_data, const char *body_text)
+{
+  struct threads_parsing_context *data = user_data;
+
+  if (body_text && *body_text)
+    VEC_last (thread_item_t, data->items)->extra = strdup (body_text);
+}
+
+const struct gdb_xml_attribute thread_attributes[] = {
+  { "id", GDB_XML_AF_NONE, NULL, NULL },
+  { "core", GDB_XML_AF_OPTIONAL, gdb_xml_parse_attr_ulongest, NULL },
+  { NULL, GDB_XML_AF_NONE, NULL, NULL }
+};
+
+const struct gdb_xml_element thread_children[] = {
+  { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+const struct gdb_xml_element threads_children[] = {
+  { "thread", thread_attributes, thread_children,
+    GDB_XML_EF_REPEATABLE | GDB_XML_EF_OPTIONAL,
+    start_thread, end_thread },
+  { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+const struct gdb_xml_element threads_elements[] = {
+  { "threads", NULL, threads_children,
+    GDB_XML_EF_NONE, NULL, NULL },
+  { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
 /*
  * Find all threads for info threads command.
  * Uses new thread protocol contributed by Cisco.
@@ -2320,6 +2399,66 @@ remote_threads_info (struct target_ops *ops)
   if (remote_desc == 0)		/* paranoia */
     error (_("Command can only be used when connected to the remote target."));
 
+  if (remote_protocol_packets[PACKET_qXfer_threads].support == PACKET_ENABLE)
+    {
+      char *xml = target_read_stralloc (&current_target, 
+					 TARGET_OBJECT_THREADS, NULL);
+
+      struct cleanup *back_to = make_cleanup (xfree, xml);
+      if (xml && *xml) 
+	{
+	  struct gdb_xml_parser *parser;
+	  struct threads_parsing_context context;
+	  struct cleanup *back_to = make_cleanup (null_cleanup, NULL);
+
+	  context.items = 0;
+	  parser = gdb_xml_create_parser_and_cleanup (_("threads"),
+						      threads_elements,
+						      &context);
+
+	  gdb_xml_use_dtd (parser, "threads.dtd");
+	  
+	  if (gdb_xml_parse (parser, xml) == 0)
+	    {
+	      int i;
+	      struct thread_item *item;
+
+	      for (i = 0; VEC_iterate (thread_item_t, context.items, i, item); ++i)
+		{
+		  if (!ptid_equal (item->ptid, null_ptid))
+		    {
+		      struct thread_info *info;
+		      /* In non-stop mode, we assume new found threads
+			 are running until proven otherwise with a
+			 stop reply.  In all-stop, we can only get
+			 here if all threads are stopped.  */
+		      int running = non_stop ? 1 : 0;
+
+		      remote_notice_new_inferior (item->ptid, running);
+
+		      info = find_thread_ptid (item->ptid);
+		      if (info)
+			{
+			  if (!info->private)
+			    info->private = (struct private_thread_info *)
+			      xmalloc (sizeof (struct private_thread_info));
+
+			  info->private->extra = item->extra;
+			  item->extra = 0;
+			  info->private->core = item->core;
+			}
+		    }
+		  xfree (item->extra);
+		}	      
+	    }
+	  
+	  VEC_free (thread_item_t, context.items);	  
+	}
+
+      do_cleanups (back_to);
+      return;
+    }
+
   if (use_threadinfo_query)
     {
       putpkt ("qfThreadInfo");
@@ -2392,6 +2531,15 @@ remote_threads_extra_info (struct thread_info *tp)
        server doesn't know about it.  */
     return NULL;
 
+  if (remote_protocol_packets[PACKET_qXfer_threads].support == PACKET_ENABLE)
+    {
+      struct thread_info *info = find_thread_ptid (tp->ptid);
+      if (info && info->private)
+	return info->private->extra;
+      else
+	return NULL;
+    }
+
   if (use_threadextra_query)
     {
       char *b = rs->buf;
@@ -3152,6 +3300,8 @@ static struct protocol_feature remote_protocol_features[] = {
     PACKET_qXfer_spu_write },
   { "qXfer:osdata:read", PACKET_DISABLE, remote_supported_packet,
     PACKET_qXfer_osdata },
+  { "qXfer:threads:read", PACKET_DISABLE, remote_supported_packet,
+    PACKET_qXfer_threads },
   { "QPassSignals", PACKET_DISABLE, remote_supported_packet,
     PACKET_QPassSignals },
   { "QStartNoAckMode", PACKET_DISABLE, remote_supported_packet,
@@ -4262,6 +4412,8 @@ struct stop_reply
 
   int solibs_changed;
   int replay_event;
+
+  int core;
 };
 
 /* The list of already fetched and acknowledged stop events.  */
@@ -4425,6 +4577,7 @@ remote_parse_stop_reply (char *buf, struct stop_reply *event)
   event->replay_event = 0;
   event->stopped_by_watchpoint_p = 0;
   event->regcache = NULL;
+  event->core = -1;
 
   switch (buf[0])
     {
@@ -4451,7 +4604,8 @@ remote_parse_stop_reply (char *buf, struct stop_reply *event)
 	  /* If this packet is an awatch packet, don't parse the 'a'
 	     as a register number.  */
 
-	  if (strncmp (p, "awatch", strlen("awatch")) != 0)
+	  if (strncmp (p, "awatch", strlen("awatch")) != 0
+	      && strncmp (p, "core", strlen ("core") != 0))
 	    {
 	      /* Read the ``P'' register number.  */
 	      pnum = strtol (p, &p_temp, 16);
@@ -4497,6 +4651,12 @@ Packet: '%s'\n"),
 		  if (p_temp)
 		    p = p_temp;
 		}
+	      else if (strncmp (p, "core", p1 - p) == 0)
+		{
+		  ULONGEST c;
+		  p = unpack_varlen_hex (++p1, &c);
+		  event->core = c;
+		}
 	      else
 		{
 		  /* Silently skip unknown optional info.  */
@@ -4706,6 +4866,7 @@ process_stop_reply (struct stop_reply *stop_reply,
 		    struct target_waitstatus *status)
 {
   ptid_t ptid;
+  struct thread_info *info;
 
   *status = stop_reply->ws;
   ptid = stop_reply->ptid;
@@ -4736,6 +4897,12 @@ process_stop_reply (struct stop_reply *stop_reply,
       remote_stopped_by_watchpoint_p = stop_reply->stopped_by_watchpoint_p;
       remote_watch_data_address = stop_reply->watch_data_address;
 
+      /* Update the core associated with a thread when we process stop
+	 event in that thread.  */
+      info = find_thread_ptid (ptid);
+      if (info && info->private)
+	info->private->core = stop_reply->core;
+
       remote_notice_new_inferior (ptid, 0);
     }
 
@@ -7579,6 +7746,11 @@ remote_xfer_partial (struct target_ops *ops, enum target_object object,
        (ops, "osdata", annex, readbuf, offset, len,
         &remote_protocol_packets[PACKET_qXfer_osdata]);
 
+    case TARGET_OBJECT_THREADS:
+      gdb_assert (annex == NULL);
+      return remote_read_qxfer (ops, "threads", annex, readbuf, offset, len,
+				&remote_protocol_packets[PACKET_qXfer_threads]);
+
     default:
       return -1;
     }
@@ -8895,6 +9067,15 @@ remote_supports_cond_tracepoints (void)
   return rs->cond_tracepoints;
 }
 
+static int
+remote_core_of_thread (struct target_ops *ops, ptid_t ptid)
+{
+  struct thread_info *info = find_thread_ptid (ptid);
+  if (info && info->private)
+    return info->private->core;
+  return -1;
+}
+
 static void
 init_remote_ops (void)
 {
@@ -8958,6 +9139,7 @@ Specify the serial device it is connected to\n\
   remote_ops.to_terminal_ours = remote_terminal_ours;
   remote_ops.to_supports_non_stop = remote_supports_non_stop;
   remote_ops.to_supports_multi_process = remote_supports_multi_process;
+  remote_ops.to_core_of_thread = remote_core_of_thread;
 }
 
 /* Set up the extended remote vector by making a copy of the standard
@@ -9317,6 +9499,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
   add_packet_config_cmd (&remote_protocol_packets[PACKET_qXfer_osdata],
                         "qXfer:osdata:read", "osdata", 0);
 
+  add_packet_config_cmd (&remote_protocol_packets[PACKET_qXfer_threads],
+			 "qXfer:threads:read", "threads", 0);
+
   add_packet_config_cmd (&remote_protocol_packets[PACKET_qXfer_siginfo_read],
                          "qXfer:siginfo:read", "read-siginfo-object", 0);
 
diff --git a/gdb/target.c b/gdb/target.c
index cd1614b..4fd82b9 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -3024,6 +3024,26 @@ target_store_registers (struct regcache *regcache, int regno)
   noprocess ();
 }
 
+int
+target_core_of_thread (ptid_t ptid)
+{
+  struct target_ops *t;
+
+  for (t = current_target.beneath; t != NULL; t = t->beneath)
+    {
+      if (t->to_core_of_thread != NULL)
+	{
+	  int retval = t->to_core_of_thread (t, ptid);
+	  if (targetdebug)
+	    fprintf_unfiltered (gdb_stdlog, "target_core_of_thread (%d) = %d\n",
+				PIDGET (ptid), retval);
+	  return retval;
+	}
+    }
+
+  return -1;
+}
+
 static void
 debug_to_prepare_to_store (struct regcache *regcache)
 {
diff --git a/gdb/target.h b/gdb/target.h
index ebe6056..7435433 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -255,7 +255,9 @@ enum target_object
   /* Extra signal info.  Usually the contents of `siginfo_t' on unix
      platforms.  */
   TARGET_OBJECT_SIGNAL_INFO,
-  /* Possible future objects: TARGET_OBJECT_FILE, ... */
+  /* The list of threads that are being debugged.  */
+  TARGET_OBJECT_THREADS,
+  /* Possible future objects: TARGET_OBJECT_FILE, ... */  
 };
 
 /* Request that OPS transfer up to LEN 8-bit bytes of the target's
@@ -597,6 +599,14 @@ struct target_ops
     struct address_space *(*to_thread_address_space) (struct target_ops *,
 						      ptid_t);
 
+    /* Return the core that thread PTID is on, or -1 if such information
+       is not available. For a stopped thread, this is supposed to return
+       the core the thread was last running on.  For running threads, it
+       should return one of the cores that the thread was running between
+       the call to this function and return -- and if it was running on
+       several cores, any other may be returned.  */
+    int (*to_core_of_thread) (struct target_ops *, ptid_t ptid);
+
     int to_magic;
     /* Need sub-structure for target machine related rather than comm related?
      */
@@ -1246,6 +1256,9 @@ extern int target_search_memory (CORE_ADDR start_addr,
       (*current_target.to_log_command) (p);				\
   while (0)
 
+
+extern int target_core_of_thread (ptid_t ptid);
+
 /* Routines for maintenance of the target structures...
 
    add_target:   Add a target to the list of all possible targets.
diff --git a/gdb/thread.c b/gdb/thread.c
index 689e9d5..fc2ee2a 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -468,8 +468,7 @@ do_captured_list_thread_ids (struct ui_out *uiout, void *arg)
   struct cleanup *cleanup_chain;
   int current_thread = -1;
 
-  prune_threads ();
-  target_find_new_threads ();
+  update_thread_list ();
 
   cleanup_chain = make_cleanup_ui_out_tuple_begin_end (uiout, "thread-ids");
 
@@ -748,8 +747,7 @@ print_thread_info (struct ui_out *uiout, int requested_thread, int pid)
   char *extra_info;
   int current_thread = -1;
 
-  prune_threads ();
-  target_find_new_threads ();
+  update_thread_list ();
   current_ptid = inferior_ptid;
 
   /* We'll be switching threads temporarily.  */
@@ -759,6 +757,7 @@ print_thread_info (struct ui_out *uiout, int requested_thread, int pid)
   for (tp = thread_list; tp; tp = tp->next)
     {
       struct cleanup *chain2;
+      int core;
 
       if (requested_thread != -1 && tp->num != requested_thread)
 	continue;
@@ -817,6 +816,10 @@ print_thread_info (struct ui_out *uiout, int requested_thread, int pid)
 	  ui_out_field_string (uiout, "state", state);
 	}
 
+      core = target_core_of_thread (tp->ptid);
+      if (ui_out_is_mi_like_p (uiout) && core != -1)
+	ui_out_field_int (uiout, "core", core);
+
       do_cleanups (chain2);
     }
 
@@ -1058,8 +1061,7 @@ thread_apply_all_command (char *cmd, int from_tty)
   if (cmd == NULL || *cmd == '\000')
     error (_("Please specify a command following the thread ID list"));
 
-  prune_threads ();
-  target_find_new_threads ();
+  update_thread_list ();
 
   old_chain = make_cleanup_restore_current_thread ();
 
@@ -1245,6 +1247,13 @@ gdb_thread_select (struct ui_out *uiout, char *tidstr, char **error_message)
   return GDB_RC_OK;
 }
 
+void
+update_thread_list (void)
+{
+  prune_threads ();
+  target_find_new_threads ();
+}
+
 /* Commands with a prefix of `thread'.  */
 struct cmd_list_element *thread_cmd_list = NULL;
 
diff --git a/gdb/ui-out.c b/gdb/ui-out.c
index 19a4644..6ecdd1f 100644
--- a/gdb/ui-out.c
+++ b/gdb/ui-out.c
@@ -44,7 +44,7 @@ struct ui_out_hdr
    is always available.  Stack/nested level 0 is reserved for the
    top-level result. */
 
-enum { MAX_UI_OUT_LEVELS = 6 };
+enum { MAX_UI_OUT_LEVELS = 8 };
 
 struct ui_out_level
   {
diff --git a/gdb/utils.c b/gdb/utils.c
index 16ad084..c9aa612 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -3554,6 +3554,14 @@ gdb_buildargv (const char *s)
   return argv;
 }
 
+int
+compare_positive_ints (const void *ap, const void *bp)
+{
+  /* Because we know we're comparing two ints which are positive,
+     there's no danger of overflow here.  */
+  return * (int *) ap - * (int *) bp;
+}
+
 /* Provide a prototype to silence -Wmissing-prototypes.  */
 extern initialize_file_ftype _initialize_utils;
 

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]