From dc146f7c09d1369f5621e4aac23d410d40d3a6f0 Mon Sep 17 00:00:00 2001 From: Vladimir Prus Date: Tue, 12 Jan 2010 21:40:25 +0000 Subject: [PATCH] Implement core awareness. * bcache.c (compare_ints): Remove (print_percentage): Use compare_positive_ints. * defs.h (compare_positive_ints): Declare. * linux-nat.h (struct lin_lwp): New field core. (linux_nat_core_of_thread_1): Declare. * linux-nat.c (add_lwp): Init the 'core' field. (linux_nat_wait_1): Record the core. (linux_nat_core_of_thread_1, linux_nat_core_of_thread): New. (linux_nat_add_target): Register the above. * linux-thread-db.c (update_thread_core): New. (thread_db_find_new_threads): Update core information for every thread. * remote.c (struct private_thread_info): New. (free_private_thread_info, demand_private_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. * 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. --- gdb/ChangeLog | 52 +++++ gdb/Makefile.in | 3 +- gdb/bcache.c | 13 +- gdb/defs.h | 2 + gdb/doc/ChangeLog | 10 + gdb/doc/gdb.texinfo | 190 ++++++++++++++-- gdb/gdbserver/ChangeLog | 14 ++ gdb/gdbserver/linux-low.c | 274 +++++++++++++++++++++--- gdb/gdbserver/remote-utils.c | 22 ++ gdb/gdbserver/server.c | 120 +++++++++++ gdb/gdbserver/target.h | 3 + gdb/gdbthread.h | 6 + gdb/linux-nat.c | 73 +++++++ gdb/linux-nat.h | 6 + gdb/linux-thread-db.c | 9 + gdb/mi/mi-interp.c | 5 + gdb/mi/mi-main.c | 357 +++++++++++++++++++++++++++---- gdb/remote.c | 218 ++++++++++++++++++- gdb/target.c | 20 ++ gdb/target.h | 13 ++ gdb/testsuite/ChangeLog | 5 + gdb/testsuite/lib/mi-support.exp | 2 +- gdb/thread.c | 30 ++- gdb/ui-out.c | 2 +- gdb/utils.c | 8 + 25 files changed, 1329 insertions(+), 128 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index f771fe05c79..d13232c7ae2 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,55 @@ +2010-01-13 Vladimir Prus + + Implement core awareness. + + * bcache.c (compare_ints): Remove + (print_percentage): Use compare_positive_ints. + * defs.h (compare_positive_ints): Declare. + * linux-nat.h (struct lin_lwp): New field core. + (linux_nat_core_of_thread_1): Declare. + * linux-nat.c (add_lwp): Init the 'core' field. + (linux_nat_wait_1): Record the core. + (linux_nat_core_of_thread_1, linux_nat_core_of_thread): New. + (linux_nat_add_target): Register the above. + * linux-thread-db.c (update_thread_core): New. + (thread_db_find_new_threads): Update core information for + every thread. + * remote.c (struct private_thread_info): New. + (free_private_thread_info, demand_private_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. + * 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. + 2010-01-12 Jan Kratochvil * configure: Regenerate (for _STRUCTURED_PROC). diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 4d3e02acdb7..31a0f76f738 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 1bc2eba8204..4badf6edf02 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 5d251b59a84..b0a212dd28b 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/ChangeLog b/gdb/doc/ChangeLog index 0de9f247af2..ae9ce2a2739 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,13 @@ +2010-01-13 Vladimir Prus + + * 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. + 2010-01-06 Stan Shebs * gdb.texinfo (Starting and Stopping Trace Experiments): Document diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 5b78f507220..02e2bbd942d 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -15542,6 +15542,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 @@ -21828,6 +21832,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 @@ -21920,7 +21925,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: @@ -21960,7 +21965,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}" @@ -22037,6 +22044,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 @@ -26349,20 +26384,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 @@ -26376,6 +26475,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 @@ -28125,6 +28234,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 @@ -29024,6 +29134,10 @@ two-digit hex number. 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 @@ -29058,8 +29172,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} @@ -29599,6 +29711,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{-} @@ -29682,6 +29800,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}). @@ -29879,6 +30001,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}. @@ -31909,6 +32040,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 @@ -32468,6 +32623,7 @@ An example document is: 1 root /sbin/init + 1,2,3 @end smallexample @@ -32475,7 +32631,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/gdbserver/ChangeLog b/gdb/gdbserver/ChangeLog index 3ec151c2fb2..f9479377a39 100644 --- a/gdb/gdbserver/ChangeLog +++ b/gdb/gdbserver/ChangeLog @@ -1,3 +1,17 @@ +2010-01-13 Vladimir Prus + + * 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_of_thread. + 2010-01-04 Ulrich Weigand * Makefile.in (clean): Remove new generated files. diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c index c20322e6540..558469162bd 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 { @@ -2801,6 +2802,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, @@ -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 9b5bad868b5..76953aed42b 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_of_thread) + core = (*the_target->core_of_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 92541216c70..c7815521448 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_of_thread) + core = (*the_target->core_of_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 ad21eb7eb4e..0c761e91f2f 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_of_thread) (ptid_t); }; extern struct target_ops *the_target; diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index 4ab211174a8..cd24eaf2fb0 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 48ea1bc46c8..bf0a5f1bdc5 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -1184,6 +1184,7 @@ add_lwp (ptid_t ptid) lp->waitstatus.kind = TARGET_WAITKIND_IGNORE; lp->ptid = ptid; + lp->core = -1; lp->next = lwp_list; lwp_list = lp; @@ -3642,6 +3643,7 @@ retry: fprintf_unfiltered (gdb_stdlog, "LLW: exit\n"); restore_child_signals_mask (&prev_mask); + lp->core = linux_nat_core_of_thread_1 (lp->ptid); return lp->ptid; } @@ -5423,6 +5425,75 @@ linux_nat_thread_address_space (struct target_ops *t, ptid_t ptid) return inf->aspace; } +int +linux_nat_core_of_thread_1 (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; +} + +/* Return the cached value of the processor core for thread PTID. */ + +int +linux_nat_core_of_thread (struct target_ops *ops, ptid_t ptid) +{ + struct lwp_info *info = find_lwp_pid (ptid); + if (info) + return info->core; + return -1; +} + void linux_nat_add_target (struct target_ops *t) { @@ -5463,6 +5534,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/linux-nat.h b/gdb/linux-nat.h index 9537740c553..5aba089eaf3 100644 --- a/gdb/linux-nat.h +++ b/gdb/linux-nat.h @@ -89,6 +89,9 @@ struct lwp_info - TARGET_WAITKIND_SYSCALL_RETURN */ int syscall_state; + /* The processor core this LWP was last seen on. */ + int core; + /* Next LWP in list. */ struct lwp_info *next; }; @@ -163,3 +166,6 @@ void linux_nat_switch_fork (ptid_t new_ptid); /* Return the saved siginfo associated with PTID. */ struct siginfo *linux_nat_get_siginfo (ptid_t ptid); + +/* Compute and return the processor core of a given thread. */ +int linux_nat_core_of_thread_1 (ptid_t ptid); diff --git a/gdb/linux-thread-db.c b/gdb/linux-thread-db.c index 659d99d9347..2c66da7e2be 100644 --- a/gdb/linux-thread-db.c +++ b/gdb/linux-thread-db.c @@ -1454,6 +1454,12 @@ thread_db_find_new_threads_1 (ptid_t ptid) thread_db_find_new_threads_2 (ptid, 0); } +static int +update_thread_core (struct lwp_info *info, void *closure) +{ + info->core = linux_nat_core_of_thread_1 (info->ptid); + return 0; +} static void thread_db_find_new_threads (struct target_ops *ops) @@ -1466,6 +1472,9 @@ thread_db_find_new_threads (struct target_ops *ops) return; thread_db_find_new_threads_1 (inferior_ptid); + + iterate_over_lwps (minus_one_ptid /* iterate over all */, + update_thread_core, NULL); } static char * diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c index 96e458c3bd0..41388bb841b 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 16f61020747..aee246ceb71 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 9c50f7ef928..f492082b8f3 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" @@ -324,6 +325,20 @@ 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); +} + /* Returns true if the multi-process extensions are in effect. */ static int remote_multi_process_p (struct remote_state *rs) @@ -1121,6 +1136,7 @@ enum { PACKET_qXfer_spu_read, PACKET_qXfer_spu_write, PACKET_qXfer_osdata, + PACKET_qXfer_threads, PACKET_qGetTLSAddr, PACKET_qSupported, PACKET_QPassSignals, @@ -1395,7 +1411,7 @@ remote_notice_new_inferior (ptid_t currthread, int running) remote_add_thread (currthread, running); inferior_ptid = currthread; } - return; + return; } if (ptid_equal (magic_null_ptid, inferior_ptid)) @@ -1405,7 +1421,7 @@ remote_notice_new_inferior (ptid_t currthread, int running) doesn't support qC. This is the first stop reported after an attach, so this is the main thread. Update the ptid in the thread list. */ - thread_change_ptid (inferior_ptid, currthread); + thread_change_ptid (inferior_ptid, currthread); return; } @@ -1427,6 +1443,26 @@ remote_notice_new_inferior (ptid_t currthread, int running) } } +/* Return the private thread data, creating it if necessary. */ + +struct private_thread_info * +demand_private_info (ptid_t ptid) +{ + struct thread_info *info = find_thread_ptid (ptid); + + gdb_assert (info); + + if (!info->private) + { + info->private = xmalloc (sizeof (*(info->private))); + info->private_dtor = free_private_thread_info; + info->private->core = -1; + info->private->extra = 0; + } + + return info->private; +} + /* Call this function as a result of 1) A halt indication (T packet) containing a thread id 2) A direct query of currthread @@ -1437,12 +1473,6 @@ static void record_currthread (ptid_t currthread) { general_thread = currthread; - - if (ptid_equal (currthread, minus_one_ptid)) - /* We're just invalidating the local thread mirror. */ - return; - - remote_notice_new_inferior (currthread, 0); } static char *last_pass_packet; @@ -2371,6 +2401,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. @@ -2388,6 +2492,61 @@ 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 private_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 = demand_private_info (item->ptid); + info->core = item->core; + info->extra = item->extra; + item->extra = 0; + } + xfree (item->extra); + } + } + + VEC_free (thread_item_t, context.items); + } + + do_cleanups (back_to); + return; + } +#endif + if (use_threadinfo_query) { putpkt ("qfThreadInfo"); @@ -2460,6 +2619,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; @@ -3245,6 +3413,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, @@ -4359,6 +4529,8 @@ struct stop_reply int solibs_changed; int replay_event; + + int core; }; /* The list of already fetched and acknowledged stop events. */ @@ -4522,6 +4694,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]) { @@ -4548,7 +4721,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); @@ -4594,6 +4768,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. */ @@ -4803,6 +4983,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; @@ -4834,6 +5015,7 @@ process_stop_reply (struct stop_reply *stop_reply, remote_watch_data_address = stop_reply->watch_data_address; remote_notice_new_inferior (ptid, 0); + demand_private_info (ptid)->core = stop_reply->core; } stop_reply_xfree (stop_reply); @@ -7676,6 +7858,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; } @@ -9324,6 +9511,15 @@ remote_set_disconnected_tracing (int val) error (_("Target does not support this command.")); } +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) { @@ -9397,6 +9593,7 @@ Specify the serial device it is connected to\n\ remote_ops.to_trace_find = remote_trace_find; remote_ops.to_get_trace_state_variable_value = remote_get_trace_state_variable_value; remote_ops.to_set_disconnected_tracing = remote_set_disconnected_tracing; + remote_ops.to_core_of_thread = remote_core_of_thread; } /* Set up the extended remote vector by making a copy of the standard @@ -9933,6 +10130,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 25a2cd736d6..edf86970f00 100644 --- a/gdb/target.c +++ b/gdb/target.c @@ -3064,6 +3064,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 20cbe29eb97..a020bf73255 100644 --- a/gdb/target.h +++ b/gdb/target.h @@ -256,6 +256,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, ... */ }; @@ -651,6 +653,14 @@ struct target_ops disconnection - set VAL to 1 to keep tracing, 0 to stop. */ void (*to_set_disconnected_tracing) (int val); + /* Return the processor core that thread PTID was last seen on. + This information is updated only when: + - update_thread_list is called + - thread stops + 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? */ @@ -1332,6 +1342,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/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 789043f9294..d40a2ffb78e 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2010-01-13 Vladimir Prus + + * lib/mi-support.exp (mi_check_thread_states): Handle + core number in thread listing. + 2010-01-12 Joel Brobecker * gdb.base/maint.exp: Adjust the expected output for the diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp index 877a10ffa03..373b3663b67 100644 --- a/gdb/testsuite/lib/mi-support.exp +++ b/gdb/testsuite/lib/mi-support.exp @@ -1877,7 +1877,7 @@ proc mi_check_thread_states { xstates test } { foreach s $states { set pattern "${pattern}(.*)state=\"$s\"" } - set pattern "$pattern\\\}\\\].*" + set pattern "${pattern}(,core=\"\[0-9\]*\")?\\\}\\\].*" verbose -log "expecting: $pattern" mi_gdb_test "-thread-info" $pattern $test diff --git a/gdb/thread.c b/gdb/thread.c index 80c378661eb..16a207c5119 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -114,10 +114,13 @@ 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? */ if (tp->private) - xfree (tp->private); + { + if (tp->private_dtor) + tp->private_dtor (tp->private); + else + xfree (tp->private); + } xfree (tp); } @@ -468,8 +471,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 +750,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 +760,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 +819,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 +1064,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 +1250,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 db8c894ef9f..e5fd4747634 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 4c03655d0a4..f72a9e3b0ed 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; -- 2.30.2