From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 21117 invoked by alias); 4 Jan 2010 15:11:39 -0000 Received: (qmail 21080 invoked by uid 22791); 4 Jan 2010 15:11:26 -0000 X-SWARE-Spam-Status: No, hits=-1.1 required=5.0 tests=AWL,BAYES_50,SPF_PASS X-Spam-Check-By: sourceware.org Received: from mail.codesourcery.com (HELO mail.codesourcery.com) (38.113.113.100) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Mon, 04 Jan 2010 15:11:10 +0000 Received: (qmail 31185 invoked from network); 4 Jan 2010 15:11:07 -0000 Received: from unknown (HELO wind.localnet) (vladimir@127.0.0.2) by mail.codesourcery.com with ESMTPA; 4 Jan 2010 15:11:07 -0000 From: Vladimir Prus To: Pedro Alves Subject: Re: [MI] core awareness Date: Mon, 04 Jan 2010 15:11:00 -0000 User-Agent: KMail/1.12.2 (Linux/2.6.31-14-generic-pae; KDE/4.3.2; i686; ; ) Cc: gdb-patches@sourceware.org References: <200912171533.21699.pedro@codesourcery.com> <200912211748.18682.vladimir@codesourcery.com> <200912311150.57763.pedro@codesourcery.com> In-Reply-To: <200912311150.57763.pedro@codesourcery.com> MIME-Version: 1.0 Content-Type: Multipart/Mixed; boundary="Boundary-00=_IUgQLR9JxubSgmP" Message-Id: <201001041811.04781.vladimir@codesourcery.com> Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2010-01/txt/msg00060.txt.bz2 --Boundary-00=_IUgQLR9JxubSgmP Content-Type: Text/Plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-length: 12156 On Thursday 31 December 2009 14:50:57 Pedro Alves wrote: > On Monday 21 December 2009 14:48:18, Vladimir Prus wrote: > > On Thursday 17 December 2009 18:33:21 Pedro Alves wrote: > > > > > > +@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, > > > > > > What's the advantage of this, rather than probing it? I would > > > see it nice for GDB to know in advance that the target can > > > report core data, but that's not that reporting support > > > for this packet means, since the "core" field is optional. > > > > Every other qXfer packet works this way. And it does not seem like much burden > > on the stub to announce that this packet is available. > > True. I'm seeing a proliferation of qSupported additions (with > the tracepoint support going in), and I get the feeling that > qSupported will end up being the largest packet a stub has > to receive/send. Maybe it's an unfounded concern. I think we still have some room before we run out of packet size limits. Anyway, it is not a problem that is introduced, or made significantly worse by this patch, so I would rather not solve it at present. > > > > + if (core != -1) > > > > + { > > > > + char s[11]; > > > > + sprintf (s, "%u", core); > > > > + > > > > + if (count == allocated) > > > > + core_numbers = realloc (core_numbers, > > > > + sizeof (int ) * (allocated *= 2)); > > > > > > No space after int. Please move the 'allocated *= 2' in its own > > > statement. My GM hat tells me that I should point out that there are a > > > bunch of hardcoded buffer sizes in the patch, that the GNU conventions > > > tells us we should avoid. > > > > I have replaced '11' with sizeof ("4294967295") + 1. > > Thanks. Though, sizeof ("4294967295") already includes the > size for the \0, so you've replaced 11 with 12. OK, noted and fixed everywhere. > > It is surely possible, but the original motivation here is mostly embedded > > systems, so I'd prefer to have gdbserver-only version checked in, and then > > anybody interested in native linux can fill the bits for linux. > > You're adding linux-nat.c:linux_nat_core_of_thread already, so we're > mostly there ... I assume it's mostly a matter of copy/paste. I'll > probably do it myself, for the sake of keeping the linux-nat.c and > linux-low.c osdata code in sync. OK, so I'll skip this part for now. > > > > + /* 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; > > > > > > Consider all-stop, multi-threaded applications: > > > > > > What about the core of the other threads? > > > Shouldn't they all be invalidated? > > > Won't then be stale? > > > > > > (see comment below) > > > > Yes, it will be stale. However, it's approximation only, and the only two > > places where cores number is used is *stopped response (which uses core of > > the stopped thread, updated above) and -list-thread-groups (which updates > > this information anyway). It does not seem right to additionally update > > this even if nothing will use it. > > Not update, _invalidate_: tag that the core is unknown and needs fetching > from the target somehow (say, adding a info->private->core_p field? > Or setting it to info->private->core = -2?). The invalidation could be > done on resume instead of on stop (e.g., linux-nat.c:resume_callback). I > believe it is bad design practice to design an API and assume > what it's callers do with it. I think it's perfectly valid to optimize the API for the use cases we expect. Otherwise, it's easy to introduce complexity and pessimization that is not necessary. > We can certainly come up with other > target_core_of_thread callers in the near future, and at that point, > we'll hit this issue. But all that lead me to the real question I have in > my mind: how will we update the core of the thread, when we implement what > I just described? Will we make remote_core_of_thread update all > threads, or come up with a special packet to get at the core of > a thread? Probably the former is okay (albeit, slow, although > done only at most once), but if not, I'd rather consider the > packet upfront, than leave a hole in the design --- if we > considered such packet, the case for qXfer:threads would get > smaller (it becomes only justifiable due to supposedly fewer > packet roundtrips to list all threads and their extra info). For a start, I certainly find that a single packet that returns all information about threads -- which a frontend might be requesting on each step -- is a good idea regardless of "core awareness". As for invalidation, I think it's reasonable to fetch information about all threads, just because the time to get that information is probably less than communication latency for a single packet. So, for avoidance of doubt, how about this scheme: - core information of each thread is invalidated when that thread is resumed (where resumption is reported by the 'target_resumed' observer) - remote_core_of_thread checks if it's asked for a core of the most recently stopped thread. If so, it reports it. Otherwise, it refreshes the thread list, and then reports core it has fetched - this all is for all-stop only. For non-stop, we get stop packet of each stopped thread and therefore remote_core_of_thread never needs to fetch information about all threads. If this sounds OK, I'll implement such a scheme. > > > > + /* 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); > > > > > > I guess that on some targets, it will be impossible to know which core > > > a running thread was running on. Should it be simply documented as > > > undefined instead? > > > > I am not sure I understand. On such target, this method will return -1. > > So, covered by the "-1 if such information is not available"? > I was assuming that meant "not available now or ever, period. No > need to request again, I'll always return -1". I see, I have clarified the wording. > > > Other: > > > > > > - did you consider being able to list the available > > > cores? A candidate for "info os cores", I guess. > > > > Unfortunately, I do not know a suitable interface to get that information. > > Parsing /proc/cpuinfo is not really attractive. > > We have more code that parses /proc/foo already though, > e.g., linux-nat.c:pid_is_stopped, and I don't suppose it would > be much worse than the new parsing of /proc/pid/task/lwp/stat > to get at the core for a thread, though. Well, while I presumably could have split /proc/cpuinfo on empty lines, and somehow print that in 'info os cores' I am unsure if we can promise any fields to be present in the output, given that man:proc does not mention any field except of "processor" and "bogomips". So, all that "info os cores" can meaningfully print is the numeric ids of cores, and this does not add very much to the information already available to the frontend. > > I attach a revision with yours and Eli's comments addressed, expect as > > mentioned above, and a delta. > > Thanks. Patch comments inline below. > > > On Monday 21 December 2009 14:48:18, Vladimir Prus wrote: > > @@ -32269,15 +32424,34 @@ An example document is: > > 1 > > root > > /sbin/init > > + 1,2,3 > > + > > + > > + 12 > > + 3 > > + > > + > > > > > > I think this part of the documentation is now stale? Yes, fixed. > > +/* Given PID, iterates over all threads in that process. > > + > > + Information about each thread, in a format suitable for qXfer:osdata:thread > > + is printed to BUFFER, if it's not NULL. The buffer will not be either > > + initialized, or finished, or have '\0' written to it. Caller is responsible > > + for such things. > > About: > > "The buffer will not be either initialized, or finished, or have '\0' written to it. > Caller is responsible for such things." > > Do you mean?: > > "The buffer is assumed to be already initialized, and the caller is responsible for > finishing and appending '\0' to it." > > ? Yes, this is better wording. > The function inits the buffer, finishes the buffer and appends \0 to it, so > I'm confused by that comment. The function does not init or finish BUFFER. It does init/finish a second buffer, but that buffer is only used internally. Am I missing something? > > + > > + buffer_grow (buffer, prev, f - prev - 1); > > + sprintf (b, "%d", i); > > + buffer_grow_str (buffer, b); > > + prev = f + 1; > > + } > > > > +/* Return the core for a thread. */ > > +static int > > Could you please add an empty new line between function describing > comment and function definition (here and elsewhere)? We're trying > to follow that style everywhere in gdb. Is that for function definitions only? Or also for function declarations and variable declarations/definitions? > > diff --git a/gdb/remote.c b/gdb/remote.c > > index 9fa92fb..e393e47 100644 > > --- a/gdb/remote.c > > +++ b/gdb/remote.c > (...) > > +/* The core number that was last seed by process_stop_reply. */ > > +static int last_core = -1; > > Is "seed" a typo here? Yes. > > > +/* The thread that corresponds to last_core. */ > > +static ptid_t thread_of_last_core; > > Are the last_core and thread_of_last_core globals needed > for when the target reports the "core" in the stop reply, > but doesn't support qxfer:threads:read? Should we simply > not care for that, and assume that a stub that wants to > report core info uses the new way to fetch thread info? > I'd prefer not to have these globals, by e.g., creating > info->private on the spot when processing the stop reply. The primary problem is that when we process a stop reply for a new thread, the thread is not created and added to gdb thread table yet, so it's not possible to set its info->private. I don't see any way around this. We can, in theory, make remote_core_of_thread request the list of all threads even when called for a thread that has just stopped, but that sounds wasteful. > >@@ -2320,6 +2409,68 @@ remote_threads_info (struct target_ops *ops) > ... > > + 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; > > I think info->private->extra is leaking when threads are deleted, > because gdb/threads.c simply xfree's ->private. I guess you're the > lucky first to need a destructor for thread private data. > nto-procfs.c was close, but avoids it by using a poor man's flexible > array member (nto-tdep.h). Done. I attach a revised patch. - Volodya --Boundary-00=_IUgQLR9JxubSgmP Content-Type: text/x-patch; charset="UTF-8"; name="core3.diff" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="core3.diff" Content-length: 60784 commit 82ec7c5a3017e306857a41e8c7ceac8dc5afdb3f Author: Vladimir Prus 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. * remote.c (struct private_thread_info): New. (free_private_thread_info): New. (last_core, thread_of_last_core): 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. * gdbthread.h (struct thread_info): New field private_dtor. * thread.c (print_thread_info): Report the core. * ui-out.c (MAX_UI_OUT_LEVELS): Increase. * utils.c (compare_positive_ints): New. * 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) (do_nothing, free_vector_of_osdata_items) (splay_tree_int_comparator, free_splay_tree): New. (print_one_inferior_data): Implemented 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, show_process, 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. (buffer_xml_printf): Support %d specifier. * 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 4d3e02a..31a0f76 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 1bc2eba..4badf6e 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 5d251b5..b0a212d 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 d0997b3..ca5e92c 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -15482,6 +15482,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 @@ -21768,6 +21772,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 @@ -21860,7 +21865,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: @@ -21900,7 +21905,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 the +processor core on which 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}" @@ -21977,6 +21984,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 + +Whenever @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 core +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 @@ -26289,20 +26324,84 @@ 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 of that group. +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 has 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}. -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 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 if the @samp{--recurse} option is +specified, and it's actually possible to obtain the threads. + +@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 @@ -26316,6 +26415,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 @@ -28065,6 +28174,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 @@ -28965,6 +29075,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 @@ -28998,8 +29112,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} @@ -29539,6 +29651,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{-} @@ -29622,6 +29740,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}). @@ -29814,6 +29936,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}. @@ -31827,6 +31958,30 @@ The formal DTD for memory map format is given below: @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} issues the @samp{qXfer:threads:read} packet +(@pxref{qXfer threads read}) and obtains the XML document with +the following structure: + +@smallexample + + + + ... description ... + + +@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 processor core +the thread was last executing on. The content of the of @samp{thread} +element is interpreted as human-readable auxilliary information. + @include agentexpr.texi @node Target Descriptions @@ -32386,6 +32541,7 @@ An example document is: 1 root /sbin/init + 1,2,3 @end smallexample @@ -32393,7 +32549,9 @@ An example document is: Each 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, +displayed by @value{GDBN}. The @samp{cores} column, if present, +should contain a comma-separated list of cores that this process +is running on. Target may provide additional columns, which @value{GDBN} currently ignores. @include gpl.texi diff --git a/gdb/features/threads.dtd b/gdb/features/threads.dtd new file mode 100644 index 0000000..20308ee --- /dev/null +++ b/gdb/features/threads.dtd @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c index c20322e..5584691 100644 --- a/gdb/gdbserver/linux-low.c +++ b/gdb/gdbserver/linux-low.c @@ -140,6 +140,7 @@ static int check_removed_breakpoint (struct lwp_info *event_child); static void *add_lwp (ptid_t ptid); 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 { @@ -2802,6 +2803,175 @@ 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; +} + +static int * +unique (int *b, int *e) +{ + int *d = b; + while (++b != e) + if (*d != *b) + *++d = *b; + return ++d; +} + +/* Given PID, iterates over all threads in that process. + + Information about each thread, in a format suitable for qXfer:osdata:thread + is printed to BUFFER, if it's not NULL. BUFFER is assumed to be already + initialized, and the caller is responsible for finishing and appending '\0' + to it. + + The list of cores that threads are running on is assigned to *CORES, if it + is not NULL. If no cores are found, *CORES will be set to NULL. Caller + should free *CORES. */ + +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) + { + free (core_numbers); + return; + } + + 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[sizeof ("4294967295")]; + sprintf (s, "%u", core); + + if (count == allocated) + { + allocated *= 2; + core_numbers = realloc (core_numbers, + sizeof (int) * allocated); + } + core_numbers[count++] = core; + if (buffer) + buffer_xml_printf (buffer, + "" + "%d" + "%s" + "%s" + "", pid, dp->d_name, s); + } + else + { + if (buffer) + buffer_xml_printf (buffer, + "" + "%d" + "%s" + "", pid, dp->d_name); + } + } + } + } + + if (cores) + { + *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 = unique (b, core_numbers + count); + + buffer_init (&buffer2); + + for (b = core_numbers; b != e; ++b) + { + char number[sizeof ("4294967295")]; + sprintf (number, "%u", *b); + buffer_xml_printf (&buffer2, "%s%s", + (b == core_numbers) ? "" : ",", number); + } + buffer_grow_str0 (&buffer2, ""); + + *cores = buffer_finish (&buffer2); + } + } + free (core_numbers); +} + +static void +show_process (int pid, const char *username, struct buffer *buffer) +{ + char pathname[128]; + FILE *f; + char cmd[MAXPATHLEN + 1]; + + sprintf (pathname, "/proc/%d/cmdline", pid); + + if ((f = fopen (pathname, "r")) != NULL) + { + size_t len = fread (cmd, 1, sizeof (cmd) - 1, f); + if (len > 0) + { + char *cores = 0; + int i; + for (i = 0; i < len; i++) + if (cmd[i] == '\0') + cmd[i] = ' '; + cmd[len] = '\0'; + + buffer_xml_printf (buffer, + "" + "%d" + "%s" + "%s", + pid, + username, + cmd); + + /* This only collects core numbers, and does not print threads. */ + list_threads (pid, NULL, &cores); + + if (cores) + { + buffer_xml_printf (buffer, + "%s", cores); + free (cores); + } + + buffer_xml_printf (buffer, ""); + } + fclose (f); + } +} + +static int linux_qxfer_osdata (const char *annex, unsigned char *readbuf, unsigned const char *writebuf, CORE_ADDR offset, int len) @@ -2811,10 +2981,16 @@ linux_qxfer_osdata (const char *annex, static const char *buf; static long len_avail = -1; static struct buffer buffer; + int processes = 0; + int threads = 0; DIR *dirp; - if (strcmp (annex, "processes") != 0) + if (strcmp (annex, "processes") == 0) + processes = 1; + else if (strcmp (annex, "threads") == 0) + threads = 1; + else return 0; if (!readbuf || writebuf) @@ -2827,7 +3003,10 @@ linux_qxfer_osdata (const char *annex, len_avail = 0; buf = NULL; buffer_init (&buffer); - buffer_grow_str (&buffer, ""); + if (processes) + buffer_grow_str (&buffer, ""); + else if (threads) + buffer_grow_str (&buffer, ""); dirp = opendir ("/proc"); if (dirp) @@ -2846,37 +3025,16 @@ linux_qxfer_osdata (const char *annex, if (stat (procentry, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)) { - char pathname[128]; - FILE *f; - char cmd[MAXPATHLEN + 1]; - struct passwd *entry; - - sprintf (pathname, "/proc/%s/cmdline", dp->d_name); - entry = getpwuid (statbuf.st_uid); + int pid = (int) strtoul (dp->d_name, NULL, 10); - if ((f = fopen (pathname, "r")) != NULL) + if (processes) { - size_t len = fread (cmd, 1, sizeof (cmd) - 1, f); - if (len > 0) - { - int i; - for (i = 0; i < len; i++) - if (cmd[i] == '\0') - cmd[i] = ' '; - cmd[len] = '\0'; - - buffer_xml_printf ( - &buffer, - "" - "%s" - "%s" - "%s" - "", - dp->d_name, - entry ? entry->pw_name : "?", - cmd); - } - fclose (f); + struct passwd *entry = getpwuid (statbuf.st_uid); + show_process (pid, entry ? entry->pw_name : "?", &buffer); + } + else if (threads) + { + list_threads (pid, &buffer, NULL); } } } @@ -3152,6 +3310,55 @@ 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"); + if (!f) + return -1; + + 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, @@ -3191,10 +3398,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 9b5bad8..d638a3f 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); + } } } @@ -1604,6 +1616,16 @@ buffer_xml_printf (struct buffer *buffer, const char *format, ...) prev = f + 1; } break; + case 'd': + { + int i = va_arg (ap, int); + char b[sizeof ("4294967295")]; + + buffer_grow (buffer, prev, f - prev - 1); + sprintf (b, "%d", i); + buffer_grow_str (buffer, b); + prev = f + 1; + } } percent = 0; } diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c index 9254121..dfc92a8 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, "\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, "\n", + ptid_s, core_s); + } + else + { + buffer_xml_printf (buffer, "\n", + ptid_s); + } + } + + buffer_grow_str0 (buffer, "\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 ad21eb7..0ee0d41 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 4ab2111..cd24eaf 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -187,6 +187,10 @@ struct thread_info /* Private data used by the target vector implementation. */ struct private_thread_info *private; + + /* Function that is called to free PRIVATE. If this is NULL, then + xfree will be called on PRIVATE. */ + void (*private_dtor) (struct private_thread_info *); }; /* Create an empty thread list, or empty the existing one. */ @@ -346,4 +350,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 0c95b95..78b1303 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -5423,6 +5423,66 @@ 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"); + if (!f) + { + do_cleanups (back_to); + return -1; + } + + 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) { @@ -5463,6 +5523,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 96e458c..41388bb 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 16f6102..aee246c 100644 --- a/gdb/mi/mi-main.c +++ b/gdb/mi/mi-main.c @@ -50,6 +50,7 @@ #include "valprint.h" #include "inferior.h" #include "osdata.h" +#include "splay-tree.h" #include #include @@ -360,11 +361,55 @@ 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; +} + +static int * +unique (int *b, int *e) +{ + int *d = b; + while (++b != e) + if (*d != *b) + *++d = *b; + return ++d; +} + +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_empty (int, top_data->inferiors) + || 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); @@ -372,81 +417,299 @@ 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_empty (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); + e = unique (b, e); + + for (; b != e; ++b) + ui_out_field_int (uiout, NULL, *b); + + do_cleanups (back_to_2); + } + + if (top_data->recurse) + print_thread_info (uiout, -1, inferior->pid); + do_cleanups (back_to); } return 0; } -void -mi_cmd_list_thread_groups (char *command, char **argv, int argc) +/* 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; - int available = 0; - char *id = NULL; + struct cleanup *back_to = make_cleanup_ui_out_list_begin_end (uiout, + field_name); + char *cores = xstrdup (xcores); + char *p = cores; - if (argc > 0 && strcmp (argv[0], "--available") == 0) - { - ++argv; - --argc; - available = 1; - } + make_cleanup (xfree, cores); - if (argc > 0) - id = argv[0]; + for (p = strtok (p, ","); p; p = strtok (NULL, ",")) + ui_out_field_string (uiout, NULL, p); - back_to = make_cleanup (null_cleanup, NULL); + do_cleanups (back_to); +} - if (available && id) - { - error (_("Can only report top-level available thread groups")); - } - else if (available) - { - struct osdata *data; - struct osdata_item *item; - int ix_items; +static void +free_vector_of_ints (void *xvector) +{ + VEC (int) **vector = xvector; + VEC_free (int, *vector); +} - data = get_osdata ("processes"); - make_cleanup_osdata_free (data); +static void +do_nothing (splay_tree_key k) +{ +} - make_cleanup_ui_out_list_begin_end (uiout, "groups"); +static void +free_vector_of_osdata_items (splay_tree_value xvalue) +{ + VEC (osdata_item_s) *value = (VEC (osdata_item_s) *) xvalue; + /* We don't free the items itself, it will be done separately. */ + VEC_free (osdata_item_s, value); +} + +static int +splay_tree_int_comparator (splay_tree_key xa, splay_tree_key xb) +{ + int a = xa; + int b = xb; + return a - b; +} + +static void +free_splay_tree (void *xt) +{ + splay_tree t = xt; + splay_tree_delete (t); +} + +static void +list_available_thread_groups (VEC (int) *ids, int recurse) +{ + struct osdata *data; + struct osdata_item *item; + int ix_items; + /* This keeps a map from integer (pid) to VEC (struct osdata_item *)* + The vector contains information about all threads for the given + pid. */ + splay_tree tree; + + /* get_osdata will throw if it cannot return data. */ + data = get_osdata ("processes"); + make_cleanup_osdata_free (data); + + if (recurse) + { + struct osdata *threads = get_osdata ("threads"); + make_cleanup_osdata_free (threads); + + tree = splay_tree_new (splay_tree_int_comparator, + do_nothing, + free_vector_of_osdata_items); + make_cleanup (free_splay_tree, tree); for (ix_items = 0; - VEC_iterate (osdata_item_s, data->items, + VEC_iterate (osdata_item_s, threads->items, ix_items, item); ix_items++) { - struct cleanup *back_to = - make_cleanup_ui_out_tuple_begin_end (uiout, NULL); - const char *pid = get_osdata_column (item, "pid"); - const char *cmd = get_osdata_column (item, "command"); - const char *user = get_osdata_column (item, "user"); + int pid_i = strtoul (pid, NULL, 0); + VEC (osdata_item_s) *vec = 0; + + splay_tree_node n = splay_tree_lookup (tree, pid_i); + if (!n) + { + VEC_safe_push (osdata_item_s, vec, item); + splay_tree_insert (tree, pid_i, (splay_tree_value)vec); + } + else + { + vec = (VEC (osdata_item_s) *) n->value; + VEC_safe_push (osdata_item_s, vec, item); + n->value = (splay_tree_value) vec; + } + } + } + + make_cleanup_ui_out_list_begin_end (uiout, "groups"); + + for (ix_items = 0; + VEC_iterate (osdata_item_s, data->items, + ix_items, item); + ix_items++) + { + 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; + - ui_out_field_fmt (uiout, "id", "%s", pid); - ui_out_field_string (uiout, "type", "process"); - if (cmd) - ui_out_field_string (uiout, "description", cmd); - if (user) - ui_out_field_string (uiout, "user", user); + back_to = make_cleanup_ui_out_tuple_begin_end (uiout, NULL); - do_cleanups (back_to); + ui_out_field_fmt (uiout, "id", "%s", pid); + ui_out_field_string (uiout, "type", "process"); + if (cmd) + ui_out_field_string (uiout, "description", cmd); + if (user) + ui_out_field_string (uiout, "user", user); + if (cores) + output_cores (uiout, "cores", cores); + + if (recurse) + { + splay_tree_node n = splay_tree_lookup (tree, pid_i); + if (n) + { + VEC (osdata_item_s) *children = (VEC (osdata_item_s) *) n->value; + 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, children, 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) +} + +void +mi_cmd_list_thread_groups (char *command, char **argv, int argc) +{ + struct cleanup *back_to; + int available = 0; + int recurse = 0; + VEC (int) *ids = 0; + + enum opt { - int pid = atoi (id); + AVAILABLE_OPT, RECURSE_OPT + }; + static struct mi_opt opts[] = + { + {"-available", AVAILABLE_OPT, 0}, + {"-recurse", RECURSE_OPT, 1}, + { 0, 0, 0 } + }; + + int optind = 0; + char *optarg; + + 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, "0") == 0) + ; + else if (strcmp (optarg, "1") == 0) + recurse = 1; + else + error ("only '0' and '1' are valid values for the '--recurse' option"); + break; + } + } + + for (; optind < argc; ++optind) + { + 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); + } + if (VEC_length (int, ids) > 1) + qsort (VEC_address (int, ids), + VEC_length (int, ids), + sizeof (int), compare_positive_ints); + + back_to = make_cleanup (free_vector_of_ints, &ids); + + if (available) + { + list_available_thread_groups (ids, recurse); + } + else if (VEC_length (int, ids) == 1) + { + /* Local thread groups, single id. */ + int pid = *VEC_address (int, ids); if (!in_inferior_list (pid)) - error ("Invalid thread group id '%s'", id); - print_thread_info (uiout, -1, pid); + 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/remote.c b/gdb/remote.c index 7af67e5..9d343b2 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,26 @@ 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; +}; + +static void +free_private_thread_info (struct private_thread_info *info) +{ + xfree (info->extra); + xfree (info); +} + +/* The core number that was last seen by process_stop_reply. */ +static int last_core = -1; + +/* The thread that corresponds to last_core. */ +static ptid_t thread_of_last_core; + /* Returns true if the multi-process extensions are in effect. */ static int remote_multi_process_p (struct remote_state *rs) @@ -1054,6 +1075,7 @@ enum { PACKET_qXfer_spu_read, PACKET_qXfer_spu_write, PACKET_qXfer_osdata, + PACKET_qXfer_threads, PACKET_qGetTLSAddr, PACKET_qSupported, PACKET_QPassSignals, @@ -2303,6 +2325,80 @@ remote_find_new_threads (void) CRAZY_MAX_THREADS); } +#if defined(HAVE_LIBEXPAT) + +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 } +}; + +#endif + /* * Find all threads for info threads command. * Uses new thread protocol contributed by Cisco. @@ -2320,6 +2416,70 @@ 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 defined(HAVE_LIBEXPAT) + if (remote_protocol_packets[PACKET_qXfer_threads].support == PACKET_ENABLE) + { + char *xml = target_read_stralloc (¤t_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_dtor = free_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; + } +#endif + if (use_threadinfo_query) { putpkt ("qfThreadInfo"); @@ -2392,6 +2552,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 +3321,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 +4433,8 @@ struct stop_reply int solibs_changed; int replay_event; + + int core; }; /* The list of already fetched and acknowledged stop events. */ @@ -4425,6 +4598,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 +4625,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 +4672,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 +4887,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 +4918,19 @@ 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; + } + else + { + last_core = stop_reply->core; + thread_of_last_core = ptid; + } + remote_notice_new_inferior (ptid, 0); } @@ -7579,6 +7774,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 +9095,17 @@ 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; + else if (ptid_equal (ptid, thread_of_last_core)) + return last_core; + return -1; +} + static void init_remote_ops (void) { @@ -8958,6 +9169,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 +9529,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 38cd508..899f61a 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 9a89b93..a4fe06a 100644 --- a/gdb/target.h +++ b/gdb/target.h @@ -255,6 +255,8 @@ enum target_object /* Extra signal info. Usually the contents of `siginfo_t' on unix platforms. */ TARGET_OBJECT_SIGNAL_INFO, + /* The list of threads that are being debugged. */ + TARGET_OBJECT_THREADS, /* Possible future objects: TARGET_OBJECT_FILE, ... */ }; @@ -597,6 +599,15 @@ struct target_ops struct address_space *(*to_thread_address_space) (struct target_ops *, ptid_t); + /* Return the core that thread PTID is on. For a stopped thread, should + return the core the thread was last running on. For a running thread, + 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. + If the core cannot be determined -- either for the specified thread, or + right now, or in this debug session, or for this target -- return -1. */ + 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 +1257,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 80c3786..9613393 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -115,9 +115,14 @@ free_thread (struct thread_info *tp) clear_thread_inferior_resources (tp); /* FIXME: do I ever need to call the back-end to give it a - chance at this private data before deleting the thread? */ + chance at this private data before deleting the thread? */ if (tp->private) - xfree (tp->private); + { + if (tp->private_dtor) + tp->private_dtor (tp->private); + else + xfree (tp->private); + } xfree (tp); } @@ -468,8 +473,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 +752,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 +762,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 +821,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 +1066,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 +1252,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 db8c894..e5fd474 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 4c03655..f72a9e3 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; --Boundary-00=_IUgQLR9JxubSgmP--