Re: [RFA] -list-target-groups --available

Vladimir Prus
2008-11-22T16:40:48Z

[-- Attachment #1: Type: text/plain, Size: 1574 bytes --]

On Saturday 22 November 2008 19:38:50 Vladimir Prus wrote:
> On Thursday 20 November 2008 17:42:26 Daniel Jacobowitz wrote:
> > On Fri, Nov 14, 2008 at 08:03:33PM +0300, Vladimir Prus wrote:
> > >
> > > This patch implements MI '-list-target-groups --available' -- for
> > > native linux and remote targets. In both cases, the command actually
> > > returns the list of processes. The bulk of the patch was written by
> > > Pedro, I've just added MI bits to it.
> >
> > All the code looks OK to me. However, I would not like to add this
> > patch without testcases or documentation. There should be a DTD
> > somewhere, too.
>
> This version has DTD. It also has documentation -- Eli, can you review it.
> I did you found a good place where new "info os processes" should be documented,
> any suggestions?
>
> I've also added a minimal test for the new functionality -- it basically makes
> sure we can get some process list, when using gdbserver on linux. Given that it's
> very system-dependent, I don't think we can do better.
>
> >
> > > +void
> > > +_initialize_osdata (void)
> > > +{
> > > + add_info ("os", info_osdata_command,
> > > + _("Show OS data ARG."));
> > > +
> > > + /* An alias for "info osdata processes". */
> > > + add_info ("processes", info_processes_command,
> > > + _("List running processes on the target."));
> > > +}
> >
> > "info os processes" or "info osdata"?
>
> I wonder if the alias should be dropped -- "info processes" can
> be easily confused with "info inferiors".

And there's the patch.

- Volodya

[-- Attachment #2: list_thread_groups.patch --]
[-- Type: text/x-diff, Size: 39112 bytes --]

commit 9af661879fa93e46839c7021142de9051c003f42
Author: Vladimir Prus <vladimir@codesourcery.com>
Date: Fri Nov 14 19:42:24 2008 +0300

-list-thread-groups --available

gdb/
* Makefile.in (XMLFILES): Add osdata.dtd.
(SFILES): Add osdata.c.
(COMMON_OBS): Add osdata.o.
* linux-nat.c: Include pwd.h, sys/types.h, gdb_dirent.h and xml-support.h.
(linux_nat_xfer_osdata): New function.
(linux_xfer_partial): Handle TARGET_OBJECT_OSDATA.
* osdata.c: New file.
* osdata.h: New file.
* remote.c (PACKET_qXfer_osdata): New packet enum.
(remote_protocol_features): Add "qXfer:osdata:read".
(remote_read_qxfer): Handle TARGET_OBJECT_OSDATA.
(extended_remote_can_run): New.
(init_extended_remote_ops): Set to_can_run to
extended_remote_can_run.
(_initialize_remote): Add packet config command for
"qXfer:osdata:read".
* xml-support.c (obstack_xml_printf): New function.
* xml-support.h (obstack_xml_printf): Declare.
* target.c (target_get_osdata): New function.
* target.h (enum target_object): Add TARGET_OBJECT_OSDATA.
(target_os_data): Declare.
* features/osdata.dtd: New file.

* mi/mi-main.c (mi_list_thread_groups): Handle the --available
option.

gdb/doc/
* gdb.texinfo (Operating System Information): New appendix.

gdbserver/
* target.h (struct target_ops): Add qxfer_osdata member.
* linux-low.c: Include ctype.h and pwd.h and sys/types.h
and dirent.h.
(linux_qxfer_osdata): New functions.
(linux_target_ops): Register linux_qxfer_osdata as qxfer_osdata
callback.
* server.c (handle_query): Handle "qXfer:osdata:read:".
* remote-utils.c (buffer_grow, buffer_free, buffer_init, buffer_finish)
(buffer_xml_printf): New functions.
* server.h (struct buffer): New.
(buffer_grow_str, buffer_grow_str0): New macros.
(buffer_grow, buffer_free, buffer_init, buffer_finish)
(buffer_xml_printf): Declare.

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 5432c88..47b3be0 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -434,7 +434,7 @@ 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/library-list.dtd $(srcdir)/features/osdata.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,
@@ -637,7 +637,7 @@ SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c ada-tasks.c \
macrotab.c macroexp.c macrocmd.c macroscope.c main.c maint.c \
mdebugread.c memattr.c mem-break.c minsyms.c mipsread.c memory-map.c \
objc-exp.y objc-lang.c \
- objfiles.c osabi.c observer.c \
+ objfiles.c osabi.c observer.c osdata.c \
p-exp.y p-lang.c p-typeprint.c p-valprint.c parse.c printcmd.c \
prologue-value.c \
regcache.c reggroups.c remote.c remote-fileio.c reverse.c \
@@ -808,7 +808,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \
solib.o solib-null.o \
prologue-value.o memory-map.o xml-support.o \
target-descriptions.o target-memory.o xml-tdesc.o xml-builtin.o \
- inferior.o
+ inferior.o osdata.o

TSOBS = inflow.o

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index d96d058..5ba1ed0 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -29078,6 +29078,51 @@ contain registers @samp{ev0h} through @samp{ev31h}, @samp{acc}, and
these to present registers @samp{ev0} through @samp{ev31} to the
user.

+@node Operating System Information
+@appendix Operating System Information
+@cindex operating system information
+
+Users of @value{GDBN} often wish to obtain information about state of
+the operating system running on the target---for example the list of
+processes, or the list open files. This section describes the
+mechanism that makes it possible. This mechanism is similar to the
+target features mechanism (@pxref{Target Descriptions}), but focuses
+on different aspect of target.
+
+Operating system information is retrived from the target via the
+remote protocol, using @samp{qXfer} requests (@pxref{General Query Packets,
+qXfer}). The object name in the request should be @samp{osdata}, and
+the @var{annex} identifies the data to be fetched.
+
+@node Process list
+@section Process list
+@cindex operating system information, process list
+
+When requesting process list, the @var{annex} field in the
+@samp{qXfer} request should be @samp{processes}. The returned data is
+an XML document. The formal syntax of this document is defined in
+@file{gdb/features/gdb-target.dtd}.
+
+The example content is:
+
+@smallexample
+<?xml version="1.0"?>
+
+<osdata type="processes">
+ <item>
+ <column name="pid">1</column>
+ <column name="user">root</column>
+ <column name="command">/sbin/init</column>
+ </item>
+</osdata>
+@end smallexample
+
+Each item is required to include a column with name of @samp{pid},
+that identifies the process. The @samp{user} and @samp{command}
+columns may be provided, and will be displayed by @value{GDBN}.
+Target may provide additional columns, but no processing of them
+is performed by the current version of @value{GDBN}.
+
@include gpl.texi

@raisesections
diff --git a/gdb/features/osdata.dtd b/gdb/features/osdata.dtd
new file mode 100644
index 0000000..0a8eecb
--- /dev/null
+++ b/gdb/features/osdata.dtd
@@ -0,0 +1,15 @@
+<!-- Copyright (C) 2008 Free Software Foundation, Inc.
+
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved. -->
+
+
+
+<!ELEMENT osdata (item*)>
+<!ATTLIST osdata type CDATA #REQUIRED>
+
+<!ELEMENT item (column*)>
+
+<!ELEMENT column (#PCDATA)>
+<!ATTLIST column name CDATA #REQUIRED>
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 4766cc9..a518217 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -33,6 +33,10 @@
#include <errno.h>
#include <sys/syscall.h>
#include <sched.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <dirent.h>

#ifndef PTRACE_GETSIGINFO
# define PTRACE_GETSIGINFO 0x4202
@@ -2049,6 +2053,109 @@ linux_read_offsets (CORE_ADDR *text_p, CORE_ADDR *data_p)
}
#endif

+static int
+linux_qxfer_osdata (const char *annex,
+ unsigned char *readbuf, unsigned const char *writebuf,
+ CORE_ADDR offset, int len)
+{
+ /* We make the process list snapshot when the object starts to be
+ read. */
+ static const char *buf;
+ static long len_avail = -1;
+ static struct buffer buffer;
+
+ DIR *dirp;
+
+ if (strcmp (annex, "processes") != 0)
+ return 0;
+
+ if (!readbuf || writebuf)
+ return 0;
+
+ if (offset == 0)
+ {
+ if (len_avail != -1 && len_avail != 0)
+ buffer_free (&buffer);
+ len_avail = 0;
+ buf = NULL;
+ buffer_init (&buffer);
+ buffer_grow_str (&buffer, "<osdata type=\"processes\">");
+
+ dirp = opendir ("/proc");
+ if (dirp)
+ {
+ struct dirent *dp;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ struct stat statbuf;
+ char procentry[sizeof ("/proc/4294967295")];
+
+ if (!isdigit (dp->d_name[0])
+ || strlen (dp->d_name) > sizeof ("4294967295") - 1)
+ continue;
+
+ sprintf (procentry, "/proc/%s", dp->d_name);
+ 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);
+
+ if ((f = fopen (pathname, "r")) != NULL)
+ {
+ 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,
+ "<item>"
+ "<column name=\"pid\">%s</column>"
+ "<column name=\"user\">%s</column>"
+ "<column name=\"command\">%s</column>"
+ "</item>",
+ dp->d_name,
+ entry ? entry->pw_name : "?",
+ cmd);
+ }
+ fclose (f);
+ }
+ }
+ }
+
+ closedir (dirp);
+ }
+ buffer_grow_str0 (&buffer, "</osdata>\n");
+ buf = buffer_finish (&buffer);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the data. */
+ buffer_free (&buffer);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
static struct target_ops linux_target_ops = {
linux_create_inferior,
linux_attach,
@@ -2081,6 +2188,7 @@ static struct target_ops linux_target_ops = {
#endif
NULL,
hostio_last_error_from_errno,
+ linux_qxfer_osdata,
};

static void
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index b5665f5..d37d56d 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1291,3 +1291,95 @@ xml_escape_text (const char *text)

return result;
}
+
+void
+buffer_grow (struct buffer *buffer, const char *data, size_t size)
+{
+ char *new_buffer;
+ size_t new_buffer_size;
+
+ if (size == 0)
+ return;
+
+ new_buffer_size = buffer->buffer_size;
+
+ if (new_buffer_size == 0)
+ new_buffer_size = 1;
+
+ while (buffer->used_size + size > new_buffer_size)
+ new_buffer_size *= 2;
+ new_buffer = realloc (buffer->buffer, new_buffer_size);
+ if (!new_buffer)
+ abort ();
+ memcpy (new_buffer + buffer->used_size, data, size);
+ buffer->buffer = new_buffer;
+ buffer->buffer_size = new_buffer_size;
+ buffer->used_size += size;
+}
+
+void
+buffer_free (struct buffer *buffer)
+{
+ if (!buffer)
+ return;
+
+ free (buffer->buffer);
+ buffer->buffer = NULL;
+ buffer->buffer_size = 0;
+ buffer->used_size = 0;
+}
+
+void
+buffer_init (struct buffer *buffer)
+{
+ memset (buffer, 0, sizeof (*buffer));
+}
+
+char*
+buffer_finish (struct buffer *buffer)
+{
+ char *ret = buffer->buffer;
+ buffer->buffer = NULL;
+ buffer->buffer_size = 0;
+ buffer->used_size = 0;
+ return ret;
+}
+
+void
+buffer_xml_printf (struct buffer *buffer, const char *format, ...)
+{
+ va_list ap;
+ const char *f;
+ const char *prev;
+ int percent = 0;
+
+ va_start (ap, format);
+
+ prev = format;
+ for (f = format; *f; f++)
+ {
+ if (percent)
+ {
+ switch (*f)
+ {
+ case 's':
+ {
+ char *p;
+ char *a = va_arg (ap, char *);
+ buffer_grow (buffer, prev, f - prev - 1);
+ p = xml_escape_text (a);
+ buffer_grow_str (buffer, p);
+ free (p);
+ prev = f + 1;
+ }
+ break;
+ }
+ percent = 0;
+ }
+ else if (*f == '%')
+ percent = 1;
+ }
+
+ buffer_grow_str (buffer, prev);
+ va_end (ap);
+}
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 4adbf51..1967d64 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -768,6 +768,38 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
return;
}

+ if (the_target->qxfer_osdata != NULL
+ && strncmp ("qXfer:osdata:read:", own_buf, 18) == 0)
+ {
+ char *annex;
+ int n;
+ unsigned int len;
+ CORE_ADDR ofs;
+ unsigned char *workbuf;
+
+ strcpy (own_buf, "E00");
+ if (decode_xfer_read (own_buf + 18, &annex, &ofs, &len) < 0)
+ return;
+ if (len > PBUFSIZ - 2)
+ len = PBUFSIZ - 2;
+ workbuf = malloc (len + 1);
+ if (!workbuf)
+ return;
+
+ n = (*the_target->qxfer_osdata) (annex, workbuf, NULL, ofs, len + 1);
+ if (n < 0)
+ write_enn (own_buf);
+ else if (n > len)
+ *new_packet_len_p = write_qxfer_response
+ (own_buf, workbuf, len, 1);
+ else
+ *new_packet_len_p = write_qxfer_response
+ (own_buf, workbuf, n, 0);
+
+ free (workbuf);
+ return;
+ }
+
/* Protocol features query. */
if (strncmp ("qSupported", own_buf, 10) == 0
&& (own_buf[10] == ':' || own_buf[10] == '\0'))
@@ -792,6 +824,10 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)

if (transport_is_reliable)
strcat (own_buf, ";QStartNoAckMode+");
+
+ if (the_target->qxfer_osdata != NULL)
+ strcat (own_buf, ";qXfer:osdata:read+");
+
return;
}

diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 817b5c4..ca5530d 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -223,6 +223,40 @@ void monitor_output (const char *msg);

char *xml_escape_text (const char *text);

+/* Simple growing buffer. */
+
+struct buffer
+{
+ char *buffer;
+ size_t buffer_size; /* allocated size */
+ size_t used_size; /* actually used size */
+};
+
+/* Append DATA of size SIZE to the end of BUFFER. Grows the buffer to
+ accommodate the new data. */
+void buffer_grow (struct buffer *buffer, const char *data, size_t size);
+
+/* Release any memory held by BUFFER. */
+void buffer_free (struct buffer *buffer);
+
+/* Initialize BUFFER. BUFFER holds no memory afterwards. */
+void buffer_init (struct buffer *buffer);
+
+/* Return a pointer into BUFFER data, effectivelly transfering
+ ownership of the buffer memory to the caller. Calling buffer_free
+ afterwards has no effect on the returned data. */
+char* buffer_finish (struct buffer *buffer);
+
+/* Simple printf to BUFFER function. Current implemented formatters:
+ %s - grow an xml escaped text in OBSTACK. */
+void buffer_xml_printf (struct buffer *buffer, const char *format, ...)
+ ATTR_FORMAT (printf, 2, 3);;
+
+#define buffer_grow_str(BUFFER,STRING) \
+ buffer_grow (BUFFER, STRING, strlen (STRING))
+#define buffer_grow_str0(BUFFER,STRING) \
+ buffer_grow (BUFFER, STRING, strlen (STRING) + 1)
+
/* Functions from ``signals.c''. */
enum target_signal target_signal_from_host (int hostsig);
int target_signal_to_host_p (enum target_signal oursig);
diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
index 9f3b899..8b45f29 100644
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -188,6 +188,11 @@ struct target_ops
/* Fill BUF with an hostio error packet representing the last hostio
error. */
void (*hostio_last_error) (char *buf);
+
+ /* Read/Write OS data using qXfer packets. */
+ int (*qxfer_osdata) (const char *annex, unsigned char *readbuf,
+ unsigned const char *writebuf, CORE_ADDR offset,
+ int len);
};

extern struct target_ops *the_target;
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 913bfec..e537c6f 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -49,6 +49,10 @@
#include "inf-loop.h"
#include "event-loop.h"
#include "event-top.h"
+#include <pwd.h>
+#include <sys/types.h>
+#include "gdb_dirent.h"
+#include "xml-support.h"

#ifdef HAVE_PERSONALITY
# include <sys/personality.h>
@@ -3995,6 +3999,113 @@ linux_proc_pending_signals (int pid, sigset_t *pending, sigset_t *blocked, sigse
}

static LONGEST
+linux_nat_xfer_osdata (struct target_ops *ops, enum target_object object,
+ const char *annex, gdb_byte *readbuf,
+ const gdb_byte *writebuf, ULONGEST offset, LONGEST len)
+{
+ /* We make the process list snapshot when the object starts to be
+ read. */
+ static const char *buf;
+ static LONGEST len_avail = -1;
+ static struct obstack obstack;
+
+ DIR *dirp;
+
+ gdb_assert (object == TARGET_OBJECT_OSDATA);
+
+ if (strcmp (annex, "processes") != 0)
+ return 0;
+
+ gdb_assert (readbuf && !writebuf);
+
+ if (offset == 0)
+ {
+ if (len_avail != -1 && len_avail != 0)
+ obstack_free (&obstack, NULL);
+ len_avail = 0;
+ buf = NULL;
+ obstack_init (&obstack);
+ obstack_grow_str (&obstack, "<osdata type=\"processes\">\n");
+
+ dirp = opendir ("/proc");
+ if (dirp)
+ {
+ struct dirent *dp;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ struct stat statbuf;
+ char procentry[sizeof ("/proc/4294967295")];
+
+ if (!isdigit (dp->d_name[0])
+ || strlen (dp->d_name) > sizeof ("4294967295") - 1)
+ continue;
+
+ sprintf (procentry, "/proc/%s", dp->d_name);
+ if (stat (procentry, &statbuf) == 0
+ && S_ISDIR (statbuf.st_mode))
+ {
+ char *pathname;
+ FILE *f;
+ char cmd[MAXPATHLEN + 1];
+ struct passwd *entry;
+
+ pathname = xstrprintf ("/proc/%s/cmdline", dp->d_name);
+ entry = getpwuid (statbuf.st_uid);
+
+ if ((f = fopen (pathname, "r")) != NULL)
+ {
+ 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';
+
+ obstack_xml_printf (
+ &obstack,
+ "<item>"
+ "<column name=\"pid\">%s</column>"
+ "<column name=\"user\">%s</column>"
+ "<column name=\"command\">%s</column>"
+ "</item>",
+ dp->d_name,
+ entry ? entry->pw_name : "?",
+ cmd);
+ }
+ fclose (f);
+ }
+
+ xfree (pathname);
+ }
+ }
+
+ closedir (dirp);
+ }
+
+ obstack_grow_str0 (&obstack, "</osdata>\n");
+ buf = obstack_finish (&obstack);
+ len_avail = strlen (buf);
+ }
+
+ if (offset >= len_avail)
+ {
+ /* Done. Get rid of the obstack. */
+ obstack_free (&obstack, NULL);
+ buf = NULL;
+ len_avail = 0;
+ return 0;
+ }
+
+ if (len > len_avail - offset)
+ len = len_avail - offset;
+ memcpy (readbuf, buf + offset, len);
+
+ return len;
+}
+
+static LONGEST
linux_xfer_partial (struct target_ops *ops, enum target_object object,
const char *annex, gdb_byte *readbuf,
const gdb_byte *writebuf, ULONGEST offset, LONGEST len)
@@ -4005,6 +4116,10 @@ linux_xfer_partial (struct target_ops *ops, enum target_object object,
return procfs_xfer_auxv (ops, object, annex, readbuf, writebuf,
offset, len);

+ if (object == TARGET_OBJECT_OSDATA)
+ return linux_nat_xfer_osdata (ops, object, annex, readbuf, writebuf,
+ offset, len);
+
xfer = linux_proc_xfer_partial (ops, object, annex, readbuf, writebuf,
offset, len);
if (xfer != 0)
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 7f5ec2f..ed92559 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -48,6 +48,7 @@
#include "language.h"
#include "valprint.h"
#include "inferior.h"
+#include "osdata.h"

#include <ctype.h>
#include <sys/time.h>
@@ -378,7 +379,41 @@ mi_cmd_list_thread_groups (char *command, char **argv, int argc)

back_to = make_cleanup (&null_cleanup, NULL);

- if (id)
+ if (available && id)
+ {
+ error (_("Can only report top-level available thread groups"));
+ }
+ else if (available)
+ {
+ struct osdata *data = get_osdata ("processes");
+ struct osdata_item *item;
+ int ix_items;
+
+ 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 =
+ 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");
+
+ 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);
+
+ do_cleanups (back_to);
+ }
+ }
+ else if (id)
{
int pid = atoi (id);
if (!in_inferior_list (pid))
diff --git a/gdb/osdata.c b/gdb/osdata.c
new file mode 100644
index 0000000..761f34b
--- /dev/null
+++ b/gdb/osdata.c
@@ -0,0 +1,357 @@
+/* Routines for handling XML generic OS data provided by target.
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "defs.h"
+#include "target.h"
+#include "vec.h"
+#include "xml-support.h"
+#include "osdata.h"
+#include "gdb_string.h"
+#include "ui-out.h"
+#include "gdbcmd.h"
+
+#if !defined(HAVE_LIBEXPAT)
+
+struct osdata *
+osdata_parse (const char *xml)
+{
+ static int have_warned;
+
+ if (!have_warned)
+ {
+ have_warned = 1;
+ warning (_("Can not parse XML OS data; XML support was disabled "
+ "at compile time"));
+ }
+
+ return NULL;
+}
+
+#else /* HAVE_LIBEXPAT */
+
+#include "xml-support.h"
+
+/* Internal parsing data passed to all XML callbacks. */
+struct osdata_parsing_data
+ {
+ struct osdata *osdata;
+ char *property_name;
+ };
+
+static void
+osdata_item_clear (struct osdata_item *item)
+{
+ if (item->columns != NULL)
+ {
+ struct osdata_column *col;
+ int ix;
+ for (ix = 0;
+ VEC_iterate (osdata_column_s, item->columns,
+ ix, col);
+ ix++)
+ {
+ xfree (col->name);
+ xfree (col->value);
+ }
+ VEC_free (osdata_column_s, item->columns);
+ item->columns = NULL;
+ }
+}
+
+/* Handle the start of a <osdata> element. */
+
+static void
+osdata_start_osdata (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, VEC(gdb_xml_value_s) *attributes)
+{
+ struct osdata_parsing_data *data = user_data;
+ char *type;
+ struct osdata *osdata;
+
+ if (data->osdata)
+ gdb_xml_error (parser, _("Seen more than on osdata element"));
+
+ type = VEC_index (gdb_xml_value_s, attributes, 0)->value;
+ osdata = XZALLOC (struct osdata);
+ osdata->type = xstrdup (type);
+ data->osdata = osdata;
+}
+
+/* Handle the start of a <item> element. */
+
+static void
+osdata_start_item (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, VEC(gdb_xml_value_s) *attributes)
+{
+ struct osdata_parsing_data *data = user_data;
+ struct osdata_item item = { NULL };
+ VEC_safe_push (osdata_item_s, data->osdata->items, &item);
+}
+
+/* Handle the start of a <column> element. */
+
+static void
+osdata_start_column (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, VEC(gdb_xml_value_s) *attributes)
+{
+ struct osdata_parsing_data *data = user_data;
+ const char *name = VEC_index (gdb_xml_value_s, attributes, 0)->value;
+ data->property_name = xstrdup (name);
+}
+
+/* Handle the end of a <column> element. */
+
+static void
+osdata_end_column (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, const char *body_text)
+{
+ struct osdata_parsing_data *data = user_data;
+ struct osdata *osdata = data->osdata;
+ struct osdata_item *item = VEC_last (osdata_item_s, osdata->items);
+ struct osdata_column *col = VEC_safe_push (osdata_column_s,
+ item->columns, NULL);
+
+ /* Transfer memory ownership. NAME was already strdup'ed. */
+ col->name = data->property_name;
+ col->value = xstrdup (body_text);
+ data->property_name = NULL;
+}
+
+/* Discard the constructed osdata (if an error occurs). */
+
+static void
+clear_parsing_data (void *p)
+{
+ struct osdata_parsing_data *data = p;
+ osdata_free (data->osdata);
+ data->osdata = NULL;
+ xfree (data->property_name);
+ data->property_name = NULL;
+}
+
+/* The allowed elements and attributes for OS data object.
+ The root element is a <osdata>. */
+
+const struct gdb_xml_attribute column_attributes[] = {
+ { "name", GDB_XML_AF_NONE, NULL, NULL },
+ { NULL, GDB_XML_AF_NONE, NULL, NULL }
+};
+
+const struct gdb_xml_element item_children[] = {
+ { "column", column_attributes, NULL,
+ GDB_XML_EF_REPEATABLE | GDB_XML_EF_OPTIONAL,
+ osdata_start_column, osdata_end_column },
+ { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+const struct gdb_xml_attribute osdata_attributes[] = {
+ { "type", GDB_XML_AF_NONE, NULL, NULL },
+ { NULL, GDB_XML_AF_NONE, NULL, NULL }
+};
+
+const struct gdb_xml_element osdata_children[] = {
+ { "item", NULL, item_children,
+ GDB_XML_EF_REPEATABLE | GDB_XML_EF_OPTIONAL,
+ osdata_start_item, NULL },
+ { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+const struct gdb_xml_element osdata_elements[] = {
+ { "osdata", osdata_attributes, osdata_children,
+ GDB_XML_EF_NONE, osdata_start_osdata, NULL },
+ { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+struct osdata *
+osdata_parse (const char *xml)
+{
+ struct gdb_xml_parser *parser;
+ struct cleanup *before_deleting_result, *back_to;
+ struct osdata_parsing_data data = { NULL };
+
+ back_to = make_cleanup (null_cleanup, NULL);
+ parser = gdb_xml_create_parser_and_cleanup (_("osdata"),
+ osdata_elements, &data);
+ gdb_xml_use_dtd (parser, "osdata.dtd");
+
+ before_deleting_result = make_cleanup (clear_parsing_data, &data);
+
+ if (gdb_xml_parse (parser, xml) == 0)
+ /* Parsed successfully, don't need to delete the result. */
+ discard_cleanups (before_deleting_result);
+
+ do_cleanups (back_to);
+ return data.osdata;
+}
+#endif
+
+void
+osdata_free (struct osdata *osdata)
+{
+ if (osdata == NULL)
+ return;
+
+ if (osdata->items != NULL)
+ {
+ struct osdata_item *item;
+ int ix;
+ for (ix = 0;
+ VEC_iterate (osdata_item_s, osdata->items,
+ ix, item);
+ ix++)
+ osdata_item_clear (item);
+ VEC_free (osdata_item_s, osdata->items);
+ }
+
+ xfree (osdata);
+}
+
+struct osdata *get_osdata (const char *type)
+{
+ struct osdata * osdata = NULL;
+ char *xml = target_get_osdata (type);
+ if (xml)
+ {
+ if (xml[0] == '\0')
+ warning (_("Empty data returned by target. Wrong osdata type?"));
+
+ osdata = osdata_parse (xml);
+ }
+
+ if (!osdata)
+ error (_("Can not fetch data now.\n"));
+
+ return osdata;
+}
+
+const char *
+get_osdata_column (struct osdata_item *item, const char *name)
+{
+ struct osdata_column *col;
+ int ix_cols;
+
+ for (ix_cols = 0;
+ VEC_iterate (osdata_column_s, item->columns,
+ ix_cols, col);
+ ix_cols++)
+ if (strcmp (col->name, name) == 0)
+ return col->value;
+
+ return NULL;
+}
+
+void
+info_osdata_command (char *type, int from_tty)
+{
+ struct osdata * osdata = NULL;
+ struct cleanup *proc_tbl_chain;
+ struct osdata_item *last;
+ int ncols;
+ int nprocs;
+
+ if (type == 0)
+ /* TODO: No type could mean "list availables types". */
+ error (_("Argument required."));
+
+ osdata = get_osdata (type);
+
+ nprocs = VEC_length (osdata_item_s, osdata->items);
+
+ last = VEC_last (osdata_item_s, osdata->items);
+ if (last && last->columns)
+ ncols = VEC_length (osdata_column_s, last->columns);
+ else
+ ncols = 0;
+
+ proc_tbl_chain
+ = make_cleanup_ui_out_table_begin_end (uiout, ncols, nprocs,
+ "OSDataTable");
+
+ if (last && last->columns)
+ {
+ struct osdata_column *col;
+ int ix;
+ for (ix = 0;
+ VEC_iterate (osdata_column_s, last->columns,
+ ix, col);
+ ix++)
+ ui_out_table_header (uiout, 10, ui_left,
+ col->name, col->name);
+ }
+
+ ui_out_table_body (uiout);
+
+ if (nprocs != 0)
+ {
+ struct osdata_item *item;
+ int ix_items;
+ for (ix_items = 0;
+ VEC_iterate (osdata_item_s, osdata->items,
+ ix_items, item);
+ ix_items++)
+ {
+ struct cleanup *old_chain, *chain;
+ struct ui_stream *stb;
+ int ix_cols;
+ struct osdata_column *col;
+
+ stb = ui_out_stream_new (uiout);
+ old_chain = make_cleanup_ui_out_stream_delete (stb);
+ chain = make_cleanup_ui_out_tuple_begin_end (uiout, "item");
+
+ for (ix_cols = 0;
+ VEC_iterate (osdata_column_s, item->columns,
+ ix_cols, col);
+ ix_cols++)
+ ui_out_field_string (uiout, col->name, col->value);
+
+ do_cleanups (chain);
+ do_cleanups (old_chain);
+
+ ui_out_text (uiout, "\n");
+ }
+ }
+
+ do_cleanups (proc_tbl_chain);
+
+ osdata_free (osdata);
+}
+
+static void
+info_processes_command (char *args, int from_tty)
+{
+ info_osdata_command ("processes", from_tty);
+}
+
+extern initialize_file_ftype _initialize_osdata; /* -Wmissing-prototypes */
+
+void
+_initialize_osdata (void)
+{
+ add_info ("os", info_osdata_command,
+ _("Show OS data ARG."));
+
+ /* An alias for "info osdata processes". */
+ add_info ("processes", info_processes_command,
+ _("List running processes on the target."));
+}
diff --git a/gdb/osdata.h b/gdb/osdata.h
new file mode 100644
index 0000000..a48dca2
--- /dev/null
+++ b/gdb/osdata.h
@@ -0,0 +1,52 @@
+/* Routines for handling XML generic OS data provided by target.
+
+ Copyright (C) 2008 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef OSDATA_H
+#define OSDATA_H
+
+#include "vec.h"
+
+typedef struct osdata_column
+{
+ char *name;
+ char *value;
+} osdata_column_s;
+DEF_VEC_O(osdata_column_s);
+
+typedef struct osdata_item
+{
+ VEC(osdata_column_s) *columns;
+} osdata_item_s;
+DEF_VEC_O(osdata_item_s);
+
+struct osdata
+{
+ char *type;
+
+ VEC(osdata_item_s) *items;
+};
+typedef struct osdata *osdata_p;
+DEF_VEC_P(osdata_p);
+
+struct osdata *osdata_parse (const char *xml);
+void osdata_free (struct osdata *);
+struct osdata *get_osdata (const char *type);
+const char *get_osdata_column (struct osdata_item *item, const char *name);
+
+#endif /* OSDATA_H */
diff --git a/gdb/remote.c b/gdb/remote.c
index 4580c77..ec600e4 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -990,6 +990,7 @@ enum {
PACKET_qXfer_memory_map,
PACKET_qXfer_spu_read,
PACKET_qXfer_spu_write,
+ PACKET_qXfer_osdata,
PACKET_qGetTLSAddr,
PACKET_qSupported,
PACKET_QPassSignals,
@@ -2945,6 +2946,8 @@ static struct protocol_feature remote_protocol_features[] = {
PACKET_qXfer_spu_read },
{ "qXfer:spu:write", PACKET_DISABLE, remote_supported_packet,
PACKET_qXfer_spu_write },
+ { "qXfer:osdata:read", PACKET_DISABLE, remote_supported_packet,
+ PACKET_qXfer_osdata },
{ "QPassSignals", PACKET_DISABLE, remote_supported_packet,
PACKET_QPassSignals },
{ "QStartNoAckMode", PACKET_DISABLE, remote_supported_packet,
@@ -7341,6 +7344,13 @@ remote_xfer_partial (struct target_ops *ops, enum target_object object,
return remote_read_qxfer (ops, "memory-map", annex, readbuf, offset, len,
&remote_protocol_packets[PACKET_qXfer_memory_map]);

+ case TARGET_OBJECT_OSDATA:
+ /* Should only get here if we're connected. */
+ gdb_assert (remote_desc);
+ return remote_read_qxfer
+ (ops, "osdata", annex, readbuf, offset, len,
+ &remote_protocol_packets[PACKET_qXfer_osdata]);
+
default:
return -1;
}
@@ -8613,6 +8623,15 @@ remote_supports_multi_process (void)
return remote_multi_process_p (rs);
}

+static int
+extended_remote_can_run (void)
+{
+ if (remote_desc != NULL)
+ return 1;
+
+ return 0;
+}
+
static void
init_remote_ops (void)
{
@@ -8698,6 +8717,7 @@ Specify the serial device it is connected to (e.g. /dev/ttya).";
extended_remote_ops.to_detach = extended_remote_detach;
extended_remote_ops.to_attach = extended_remote_attach;
extended_remote_ops.to_kill = extended_remote_kill;
+ extended_remote_ops.to_can_run = extended_remote_can_run;
}

static int
@@ -9007,6 +9027,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
add_packet_config_cmd (&remote_protocol_packets[PACKET_qXfer_spu_write],
"qXfer:spu:write", "write-spu-object", 0);

+ add_packet_config_cmd (&remote_protocol_packets[PACKET_qXfer_osdata],
+ "qXfer:osdata:read", "osdata", 0);
+
add_packet_config_cmd (&remote_protocol_packets[PACKET_qGetTLSAddr],
"qGetTLSAddr", "get-thread-local-storage-address",
0);
diff --git a/gdb/target.c b/gdb/target.c
index 3901ee7..966ab7b 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2224,6 +2224,26 @@ target_supports_non_stop ()
}

+char *
+target_get_osdata (const char *type)
+{
+ char *document;
+ struct target_ops *t;
+
+ if (target_can_run (&current_target))
+ t = &current_target;
+ else
+ t = find_default_run_target ("get OS data");
+
+ if (!t)
+ return NULL;
+
+ document = target_read_stralloc (t,
+ TARGET_OBJECT_OSDATA,
+ type);
+ return document;
+}
+
static int
default_region_ok_for_hw_watchpoint (CORE_ADDR addr, int len)
{
diff --git a/gdb/target.h b/gdb/target.h
index 05b681d..65201eb 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -214,8 +214,11 @@ enum target_object
See "target-descriptions.c". ANNEX should never be empty. */
TARGET_OBJECT_AVAILABLE_FEATURES,
/* Currently loaded libraries, in XML format. */
- TARGET_OBJECT_LIBRARIES
- /* Possible future objects: TARGET_OBJECT_FILE, TARGET_OBJECT_PROC, ... */
+ TARGET_OBJECT_LIBRARIES,
+ /* Get OS specific data. The ANNEX specifies the type (running
+ processes, etc.). */
+ TARGET_OBJECT_OSDATA
+ /* Possible future objects: TARGET_OBJECT_FILE, ... */
};

/* Request that OPS transfer up to LEN 8-bit bytes of the target's
@@ -1283,6 +1286,8 @@ extern int target_resize_to_sections (struct target_ops *target,

extern void remove_target_sections (bfd *abfd);

+extern char *target_get_osdata (const char *type);
+
\f
/* Stuff that should be shared among the various remote targets. */

diff --git a/gdb/testsuite/gdb.server/ext-run.exp b/gdb/testsuite/gdb.server/ext-run.exp
index 5af5611..24ed5dd 100644
--- a/gdb/testsuite/gdb.server/ext-run.exp
+++ b/gdb/testsuite/gdb.server/ext-run.exp
@@ -44,5 +44,11 @@ gdb_test "set remote exec-file $target_exec" "" "set remote exec-file"
gdb_breakpoint main
gdb_test "run" "Breakpoint.* main .*" "continue to main"

+if { [istarget *-*-linux*] } {
+ # On Linux, gdbserver can also report the list of processes.
+# 1 +root +/sbin/init\n.*
+ gdb_test "info os processes" ".*pid +user +command.*1 +root +/sbin/init.*" "get process list"
+}
+
gdb_test "kill" "" "kill" "Kill the program being debugged.*" "y"
gdb_test "monitor exit" ""
diff --git a/gdb/xml-support.c b/gdb/xml-support.c
index 0809844..e7f6279 100644
--- a/gdb/xml-support.c
+++ b/gdb/xml-support.c
@@ -997,6 +997,45 @@ xml_escape_text (const char *text)
return result;
}

+void
+obstack_xml_printf (struct obstack *obstack, const char *format, ...)
+{
+ va_list ap;
+ const char *f;
+ const char *prev;
+ int percent = 0;
+
+ va_start (ap, format);
+
+ prev = format;
+ for (f = format; *f; f++)
+ {
+ if (percent)
+ {
+ switch (*f)
+ {
+ case 's':
+ {
+ char *p;
+ char *a = va_arg (ap, char *);
+ obstack_grow (obstack, prev, f - prev - 1);
+ p = xml_escape_text (a);
+ obstack_grow_str (obstack, p);
+ xfree (p);
+ prev = f + 1;
+ }
+ break;
+ }
+ percent = 0;
+ }
+ else if (*f == '%')
+ percent = 1;
+ }
+
+ obstack_grow_str (obstack, prev);
+ va_end (ap);
+}
+
void _initialize_xml_support (void);

void
diff --git a/gdb/xml-support.h b/gdb/xml-support.h
index bdedb86..f06b4a7 100644
--- a/gdb/xml-support.h
+++ b/gdb/xml-support.h
@@ -233,4 +233,11 @@ extern gdb_xml_attribute_handler gdb_xml_parse_attr_enum;
ULONGEST gdb_xml_parse_ulongest (struct gdb_xml_parser *parser,
const char *value);

+/* Simple printf to obstack function. Current implemented formatters:
+ %s - grow an xml escaped text in OBSTACK. */
+
+extern void obstack_xml_printf (struct obstack *obstack,
+ const char *format, ...)
+ ATTRIBUTE_PRINTF_2;
+
#endif