[patch 3/3] Implement qXfer:libraries for Linux/gdbserver #2

Jan Kratochvil
2011-10-03T21:55:57Z

Hi Paul,

this is updated:
Re: [patch] Implement qXfer:libraries for Linux/gdbserver
http://sourceware.org/ml/gdb-patches/2011-08/msg00204.html

I gave up on the unification way. solib-svr4.c just needs too much more
information than the former <library-list/> format to: (a) Make the format
compatible, (b) make the gdbserver producer meaningful to unify, (c) gdb
consumer likewise.

It could for example use absolute base address instead of the l_addr bias to
be compatible with established <library-list/>. But it would need to pass
additionally l_ld for dealing with PIE+PIC displacement and also `struct
link_map' addresses for TLS. And it would be complicated for both producer
and consumer side to construct and use the absolute library base addresses.

The testcase would benefit from some gdbserver -> gdb in-protocol channel used
for the `warning' calls in gdbserver.

No regressions on {x86_64,x86_64-m32,i686}-fedora16pre-linux-gnu and with
gdbserver and with gdbserver+PIE.

Thanks,
Jan

gdb/
2011-10-03 Paul Pluzhnikov <ppluzhnikov@google.com>
Jan Kratochvil <jan.kratochvil@redhat.com>

* features/library-list.dtd: Add attributes library-list.main-lm,
library.lm, library.l_addr and library.l_ld.
* solib-svr4.c (struct svr4_library_list): New.
[HAVE_LIBEXPAT]: Include xml-support.h.
[HAVE_LIBEXPAT] (library_list_start_library, library_list_start_list)
(library_attributes, library_list_children, library_list_attributes)
(library_list_elements, svr4_parse_libraries)
(svr4_current_sos_via_xfer_libraries): New.
[!HAVE_LIBEXPAT] (svr4_current_sos_via_xfer_libraries): New.
(svr4_read_so_list): Extend the corruption message by addresses.
(svr4_current_sos): New variable library_list, call
svr4_current_sos_via_xfer_libraries.

gdb/gdbserver/
2011-10-03 Paul Pluzhnikov <ppluzhnikov@google.com>
Jan Kratochvil <jan.kratochvil@redhat.com>

* linux-low.c (get_phdr_phnum_from_proc_auxv, get_dynamic, get_r_debug)
(read_one_ptr, struct link_map_offsets, linux_refresh_libraries): New.
(struct linux_target_ops): Install linux_qxfer_libraries.
* linux-low.h (struct process_info_private): New member r_debug.
* server.c (handle_qxfer_libraries): Call the_target->qxfer_libraries.
* target.h (struct target_ops): New member qxfer_libraries.

gdb/doc/
2011-10-03 Jan Kratochvil <jan.kratochvil@redhat.com>

* gdb.texinfo (Library List Format): Add SVR4 format description.

gdb/testsuite/
2011-10-03 Jan Kratochvil <jan.kratochvil@redhat.com>

* gdb.base/solib-corrupted.exp: Suppress test on is_remote target.
(corrupted list): Adjust the expectation.

--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -36412,15 +36412,21 @@ are loaded.

The @samp{qXfer:libraries:read} packet returns an XML document which
lists loaded libraries and their offsets. Each library has an
-associated name and one or more segment or section base addresses,
-which report where the library was loaded in memory.
+associated name and one or more segment, section base addresses or SVR4 linker
+parameters, which report where the library was loaded in memory.

For the common case of libraries that are fully linked binaries, the
library should have a list of segments. If the target supports
dynamic linking of a relocatable object file, its library XML element
should instead include a list of allocated sections. The segment or
section bases are start addresses, not relocation offsets; they do not
-depend on the library's link-time base addresses.
+depend on the library's link-time base addresses. For SVR4 systems
+are reported parameters @{lm} with address of @code{struct link_map}
+used for TLS (Thread Local Storage) access, @code{l_addr} specifying
+bias of the mapped memory address minus the prelinked base address
+and also @code{l_ld} which is memory address of the @code{PT_DYNAMIC}
+segment. SVR4 systems additionally specify @code{struct link_map}
+address of the main executable in the @code{<library-list>} element.

@value{GDBN} must be linked with the Expat library to support XML
library lists. @xref{Expat}.
@@ -36449,23 +36455,38 @@ allocated sections (.text, .data, .bss), looks like this:
</library-list>
@end smallexample

+SVR4 systems report:
+
+@smallexample
+<library-list main-lm="0xe4f8f8">
+ <library name="/lib/ld-linux.so.2" lm="0xe4f51c" l_addr="0xe2d000"
+ l_ld="0xe4eefc"/>
+ <library name="/lib/libc.so.6" lm="0xe4fbe8" l_addr="0x154000"
+ l_ld="0x152350"/>
+</library-list>
+@end smallexample
+
The format of a library list is described by this DTD:

@smallexample

<!ELEMENT library-list (library)*>
<!ATTLIST library-list version CDATA #FIXED "1.0">
+<!ATTLIST library-list main-lm CDATA #IMPLIED>
<!ELEMENT library (segment*, section*)>
<!ATTLIST library name CDATA #REQUIRED>
+<!ATTLIST library lm CDATA #IMPLIED>
+<!ATTLIST library l_addr CDATA #IMPLIED>
+<!ATTLIST library l_ld CDATA #IMPLIED>
<!ELEMENT segment EMPTY>
<!ATTLIST segment address CDATA #REQUIRED>
<!ELEMENT section EMPTY>
<!ATTLIST section address CDATA #REQUIRED>
@end smallexample

-In addition, segments and section descriptors cannot be mixed within a
-single library element, and you must supply at least one segment or
-section for each library.
+In addition, segments, section and SVR4 descriptors cannot be mixed
+within a single library element, and you must supply at least one
+segment or section or both @code{l_addr} and @code{l_ld} for each library.

@node Memory Map Format
@section Memory Map Format
--- a/gdb/features/library-list.dtd
+++ b/gdb/features/library-list.dtd
@@ -7,9 +7,13 @@

<!ELEMENT library-list (library)*>
<!ATTLIST library-list version CDATA #FIXED "1.0">
+<!ATTLIST library-list main-lm CDATA #IMPLIED>

<!ELEMENT library (segment*, section*)>
<!ATTLIST library name CDATA #REQUIRED>
+<!ATTLIST library lm CDATA #IMPLIED>
+<!ATTLIST library l_addr CDATA #IMPLIED>
+<!ATTLIST library l_ld CDATA #IMPLIED>

<!ELEMENT segment EMPTY>
<!ATTLIST segment address CDATA #REQUIRED>
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -4906,6 +4906,377 @@ linux_emit_ops (void)
return NULL;
}

+/* Extract &phdr and num_phdr in the inferior. Return 0 on success. */
+
+static int
+get_phdr_phnum_from_proc_auxv (const int pid, const int is_elf64,
+ CORE_ADDR *phdr_memaddr, int *num_phdr)
+{
+ char filename[PATH_MAX];
+ int fd;
+ const int auxv_size = is_elf64
+ ? sizeof (Elf64_auxv_t) : sizeof (Elf32_auxv_t);
+ char buf[sizeof (Elf64_auxv_t)]; /* The larger of the two. */
+
+ xsnprintf (filename, sizeof filename, "/proc/%d/auxv", pid);
+
+ fd = open (filename, O_RDONLY);
+ if (fd < 0)
+ return 1;
+
+ *phdr_memaddr = 0;
+ *num_phdr = 0;
+ while (read (fd, buf, auxv_size) == auxv_size
+ && (*phdr_memaddr == 0 || *num_phdr == 0))
+ {
+ if (is_elf64)
+ {
+ Elf64_auxv_t *const aux = (Elf64_auxv_t *) buf;
+
+ switch (aux->a_type)
+ {
+ case AT_PHDR:
+ *phdr_memaddr = aux->a_un.a_val;
+ break;
+ case AT_PHNUM:
+ *num_phdr = aux->a_un.a_val;
+ break;
+ }
+ }
+ else
+ {
+ Elf32_auxv_t *const aux = (Elf32_auxv_t *) buf;
+
+ switch (aux->a_type)
+ {
+ case AT_PHDR:
+ *phdr_memaddr = aux->a_un.a_val;
+ break;
+ case AT_PHNUM:
+ *num_phdr = aux->a_un.a_val;
+ break;
+ }
+ }
+ }
+
+ close (fd);
+
+ if (*phdr_memaddr == 0 || *num_phdr == 0)
+ {
+ warning ("Unexpected missing AT_PHDR and/or AT_PHNUM: "
+ "phdr_memaddr = %ld, phdr_num = %d",
+ (long) *phdr_memaddr, *num_phdr);
+ return 2;
+ }
+
+ return 0;
+}
+
+/* Return &_DYNAMIC (via PT_DYNAMIC) in the inferior, or 0 if not present. */
+
+static CORE_ADDR
+get_dynamic (const int pid, const int is_elf64)
+{
+ CORE_ADDR phdr_memaddr, relocation;
+ int num_phdr, i;
+ unsigned char *phdr_buf;
+ const int phdr_size = is_elf64 ? sizeof (Elf64_Phdr) : sizeof (Elf32_Phdr);
+
+ if (get_phdr_phnum_from_proc_auxv (pid, is_elf64, &phdr_memaddr, &num_phdr))
+ return 0;
+
+ gdb_assert (num_phdr < 100); /* Basic sanity check. */
+ phdr_buf = alloca (num_phdr * phdr_size);
+
+ if (linux_read_memory (phdr_memaddr, phdr_buf, num_phdr * phdr_size))
+ return 0;
+
+ /* Compute relocation: it is expected to be 0 for "regular" executables,
+ non-zero for PIE ones. */
+ relocation = -1;
+ for (i = 0; relocation == -1 && i < num_phdr; i++)
+ if (is_elf64)
+ {
+ Elf64_Phdr *const p = (Elf64_Phdr *) (phdr_buf + i * phdr_size);
+
+ if (p->p_type == PT_PHDR)
+ relocation = phdr_memaddr - p->p_vaddr;
+ }
+ else
+ {
+ Elf32_Phdr *const p = (Elf32_Phdr *) (phdr_buf + i * phdr_size);
+
+ if (p->p_type == PT_PHDR)
+ relocation = phdr_memaddr - p->p_vaddr;
+ }
+
+ if (relocation == -1)
+ {
+ warning ("Unexpected missing PT_PHDR");
+ return 0;
+ }
+
+ for (i = 0; i < num_phdr; i++)
+ {
+ if (is_elf64)
+ {
+ Elf64_Phdr *const p = (Elf64_Phdr *) (phdr_buf + i * phdr_size);
+
+ if (p->p_type == PT_DYNAMIC)
+ return p->p_vaddr + relocation;
+ }
+ else
+ {
+ Elf32_Phdr *const p = (Elf32_Phdr *) (phdr_buf + i * phdr_size);
+
+ if (p->p_type == PT_DYNAMIC)
+ return p->p_vaddr + relocation;
+ }
+ }
+
+ return 0;
+}
+
+/* Return &_r_debug in the inferior, or -1 if not present. Return value
+ can be 0 if the inferior does not yet have the library list initialized. */
+
+static CORE_ADDR
+get_r_debug (const int pid, const int is_elf64)
+{
+ CORE_ADDR dynamic_memaddr;
+ const int dyn_size = is_elf64 ? sizeof (Elf64_Dyn) : sizeof (Elf32_Dyn);
+ unsigned char buf[sizeof (Elf64_Dyn)]; /* The larger of the two. */
+
+ dynamic_memaddr = get_dynamic (pid, is_elf64);
+ if (dynamic_memaddr == 0)
+ return (CORE_ADDR) -1;
+
+ while (linux_read_memory (dynamic_memaddr, buf, dyn_size) == 0)
+ {
+ if (is_elf64)
+ {
+ Elf64_Dyn *const dyn = (Elf64_Dyn *) buf;
+
+ if (dyn->d_tag == DT_DEBUG)
+ return dyn->d_un.d_val;
+
+ if (dyn->d_tag == DT_NULL)
+ break;
+ }
+ else
+ {
+ Elf32_Dyn *const dyn = (Elf32_Dyn *) buf;
+
+ if (dyn->d_tag == DT_DEBUG)
+ return dyn->d_un.d_val;
+
+ if (dyn->d_tag == DT_NULL)
+ break;
+ }
+
+ dynamic_memaddr += dyn_size;
+ }
+
+ return (CORE_ADDR) -1;
+}
+
+/* Read one pointer from MEMADDR in the inferior. */
+
+static int
+read_one_ptr (CORE_ADDR memaddr, CORE_ADDR *ptr, int ptr_size)
+{
+ *ptr = 0;
+ return linux_read_memory (memaddr, (unsigned char *) ptr, ptr_size);
+}
+
+struct link_map_offsets
+ {
+ /* Offset and size of r_debug.r_version. */
+ int r_version_offset;
+
+ /* Offset and size of r_debug.r_map. */
+ int r_map_offset;
+
+ /* Offset to l_addr field in struct link_map. */
+ int l_addr_offset;
+
+ /* Offset to l_name field in struct link_map. */
+ int l_name_offset;
+
+ /* Offset to l_ld field in struct link_map. */
+ int l_ld_offset;
+
+ /* Offset to l_next field in struct link_map. */
+ int l_next_offset;
+
+ /* Offset to l_prev field in struct link_map. */
+ int l_prev_offset;
+ };
+
+/* Construct qXfer:libraries:read reply. */
+
+static int
+linux_qxfer_libraries (const char *annex, unsigned char *readbuf,
+ unsigned const char *writebuf,
+ CORE_ADDR offset, int len)
+{
+ char *document;
+ unsigned document_len;
+ struct process_info_private *const priv = current_process ()->private;
+ char filename[PATH_MAX];
+ int pid, is_elf64;
+
+ static const struct link_map_offsets lmo_32bit_offsets =
+ {
+ 0, /* r_version offset. */
+ 4, /* r_debug.r_map offset. */
+ 0, /* l_addr offset in link_map. */
+ 4, /* l_name offset in link_map. */
+ 8, /* l_ld offset in link_map. */
+ 12, /* l_next offset in link_map. */
+ 16 /* l_prev offset in link_map. */
+ };
+
+ static const struct link_map_offsets lmo_64bit_offsets =
+ {
+ 0, /* r_version offset. */
+ 8, /* r_debug.r_map offset. */
+ 0, /* l_addr offset in link_map. */
+ 8, /* l_name offset in link_map. */
+ 16, /* l_ld offset in link_map. */
+ 24, /* l_next offset in link_map. */
+ 32 /* l_prev offset in link_map. */
+ };
+ const struct link_map_offsets *lmo;
+
+ if (writebuf != NULL)
+ return -2;
+ if (readbuf == NULL)
+ return -1;
+
+ pid = lwpid_of (get_thread_lwp (current_inferior));
+ xsnprintf (filename, sizeof filename, "/proc/%d/exe", pid);
+ is_elf64 = elf_64_file_p (filename);
+ lmo = is_elf64 ? &lmo_64bit_offsets : &lmo_32bit_offsets;
+
+ if (priv->r_debug == 0)
+ priv->r_debug = get_r_debug (pid, is_elf64);
+
+ if (priv->r_debug == (CORE_ADDR) -1 || priv->r_debug == 0)
+ {
+ document = xstrdup ("<library-list/>\n");
+ }
+ else
+ {
+ int allocated = 1024;
+ char *p;
+ const int ptr_size = is_elf64 ? 8 : 4;
+ CORE_ADDR lm_addr, lm_prev, l_name, l_addr, l_ld, l_next, l_prev;
+ int r_version, header_done = 0;
+
+ document = xmalloc (allocated);
+ strcpy (document, "<library-list");
+ p = document + strlen (document);
+
+ r_version = 0;
+ if (linux_read_memory (priv->r_debug + lmo->r_version_offset,
+ (unsigned char *) &r_version,
+ sizeof (r_version)) != 0
+ || r_version != 1)
+ {
+ warning ("unexpected r_debug version %d", r_version);
+ goto done;
+ }
+
+ if (read_one_ptr (priv->r_debug + lmo->r_map_offset,
+ &lm_addr, ptr_size) != 0)
+ {
+ warning ("unable to read r_map from 0x%lx",
+ (long) priv->r_debug + lmo->r_map_offset);
+ goto done;
+ }
+
+ lm_prev = 0;
+ while (read_one_ptr (lm_addr + lmo->l_name_offset,
+ &l_name, ptr_size) == 0
+ && read_one_ptr (lm_addr + lmo->l_addr_offset,
+ &l_addr, ptr_size) == 0
+ && read_one_ptr (lm_addr + lmo->l_ld_offset,
+ &l_ld, ptr_size) == 0
+ && read_one_ptr (lm_addr + lmo->l_prev_offset,
+ &l_prev, ptr_size) == 0
+ && read_one_ptr (lm_addr + lmo->l_next_offset,
+ &l_next, ptr_size) == 0)
+ {
+ unsigned char libname[PATH_MAX];
+
+ if (lm_prev != l_prev)
+ {
+ warning ("Corrupted shared library list: 0x%lx != 0x%lx",
+ (long) lm_prev, (long) l_prev);
+ break;
+ }
+
+ /* Not checking for error because reading may stop before
+ we've got PATH_MAX worth of characters. */
+ libname[0] = '\0';
+ linux_read_memory (l_name, libname, sizeof (libname) - 1);
+ libname[sizeof (libname) - 1] = '\0';
+ if (libname[0] != '\0')
+ {
+ size_t len = strlen ((char *) libname);
+ char *name;
+
+ if (!header_done)
+ {
+ /* Terminate `<library-list'. */
+ *p++ = '>';
+ header_done = 1;
+ }
+
+ while (allocated < p - document + len + 100)
+ {
+ /* Expand to guarantee sufficient storage. */
+ uintptr_t document_len = p - document;
+
+ document = xrealloc (document, 2 * allocated);
+ allocated *= 2;
+ p = document + document_len;
+ }
+
+ name = xml_escape_text ((char *) libname);
+ p += sprintf (p, "<library name=\"%s\" lm=\"0x%lx\" "
+ "l_addr=\"0x%lx\" l_ld=\"0x%lx\"/>",
+ name, (unsigned long) lm_addr,
+ (unsigned long) l_addr, (unsigned long) l_ld);
+ free (name);
+ }
+ else if (lm_prev == 0)
+ {
+ sprintf (p, " main-lm=\"0x%lx\"", (unsigned long) lm_addr);
+ p = p + strlen (p);
+ }
+
+ if (l_next == 0)
+ break;
+
+ lm_prev = lm_addr;
+ lm_addr = l_next;
+ }
+ done:
+ strcpy (p, "</library-list>");
+ }
+
+ document_len = strlen (document + offset);
+ if (len > document_len)
+ len = document_len;
+
+ memcpy (readbuf, document + offset, len);
+ xfree (document);
+
+ return len;
+}
+
static struct target_ops linux_target_ops = {
linux_create_inferior,
linux_attach,
@@ -4965,7 +5336,8 @@ static struct target_ops linux_target_ops = {
linux_cancel_breakpoints,
linux_stabilize_threads,
linux_install_fast_tracepoint_jump_pad,
- linux_emit_ops
+ linux_emit_ops,
+ linux_qxfer_libraries
};

static void
--- a/gdb/gdbserver/linux-low.h
+++ b/gdb/gdbserver/linux-low.h
@@ -56,6 +56,9 @@ struct process_info_private
/* libthread_db-specific additions. Not NULL if this process has loaded
thread_db, and it is active. */
struct thread_db *thread_db;
+
+ /* &_r_debug. 0 if not yet determined. -1 if no PT_DYNAMIC in Phdrs. */
+ CORE_ADDR r_debug;
};

struct lwp_info;
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -917,6 +917,9 @@ handle_qxfer_libraries (const char *annex,
if (annex[0] != '\0' || !target_running ())
return -1;

+ if (the_target->qxfer_libraries != NULL)
+ return the_target->qxfer_libraries (annex, readbuf, writebuf, offset, len);
+
/* Over-estimate the necessary memory. Assume that every character
in the library name must be escaped. */
total_len = 64;
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -377,6 +377,11 @@ struct target_ops
/* Return the bytecode operations vector for the current inferior.
Returns NULL if bytecode compilation is not supported. */
struct emit_ops *(*emit_ops) (void);
+
+ /* Read solib info. */
+ int (*qxfer_libraries) (const char *annex, unsigned char *readbuf,
+ unsigned const char *writebuf,
+ CORE_ADDR offset, int len);
};

extern struct target_ops *the_target;
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -916,6 +916,104 @@ open_symbol_file_object (void *from_ttyp)
return 1;
}

+/* Data exchange structure for the XML parser as returned by
+ svr4_current_sos_via_xfer_libraries. */
+
+struct svr4_library_list
+{
+ struct so_list *head, **tailp;
+ CORE_ADDR main_lm;
+};
+
+#ifdef HAVE_LIBEXPAT
+
+#include "xml-support.h"
+
+/* Handle the start of a <library> element. Note: new elements are added
+ at the tail of the list, keeping the list in order. */
+
+static void
+library_list_start_library (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, VEC(gdb_xml_value_s) *attributes)
+{
+ struct svr4_library_list *list = user_data;
+ const char *name = xml_find_attribute (attributes, "name")->value;
+ ULONGEST *lmp = xml_find_attribute (attributes, "lm")->value;
+ ULONGEST *l_addrp = xml_find_attribute (attributes, "l_addr")->value;
+ ULONGEST *l_ldp = xml_find_attribute (attributes, "l_ld")->value;
+ struct so_list *new_elem;
+
+ new_elem = XZALLOC (struct so_list);
+ new_elem->lm_info = XZALLOC (struct lm_info);
+ new_elem->lm_info->lm_addr = *lmp;
+ new_elem->lm_info->l_addr_inferior = *l_addrp;
+ new_elem->lm_info->l_ld = *l_ldp;
+
+ strncpy (new_elem->so_name, name, sizeof (new_elem->so_name) - 1);
+ new_elem->so_name[sizeof (new_elem->so_name) - 1] = 0;
+ strcpy (new_elem->so_original_name, new_elem->so_name);
+
+ *list->tailp = new_elem;
+ list->tailp = &new_elem->next;
+}
+
+/* Handle the start of a <library-list> element. */
+
+static void
+library_list_start_list (struct gdb_xml_parser *parser,
+ const struct gdb_xml_element *element,
+ void *user_data, VEC(gdb_xml_value_s) *attributes)
+{
+ struct svr4_library_list *list = user_data;
+ char *version = xml_find_attribute (attributes, "version")->value;
+ struct gdb_xml_value *main_lm = xml_find_attribute (attributes, "main-lm");
+
+ if (strcmp (version, "1.0") != 0)
+ gdb_xml_error (parser,
+ _("Library list has unsupported version \"%s\""),
+ version);
+
+ if (main_lm)
+ list->main_lm = *(ULONGEST *) main_lm->value;
+}
+
+/* The allowed elements and attributes for an XML library list.
+ The root element is a <library-list>. */
+
+static const struct gdb_xml_attribute library_attributes[] =
+{
+ { "name", GDB_XML_AF_NONE, NULL, NULL },
+ { "lm", GDB_XML_AF_NONE, gdb_xml_parse_attr_ulongest, NULL },
+ { "l_addr", GDB_XML_AF_NONE, gdb_xml_parse_attr_ulongest, NULL },
+ { "l_ld", GDB_XML_AF_NONE, gdb_xml_parse_attr_ulongest, NULL },
+ { NULL, GDB_XML_AF_NONE, NULL, NULL }
+};
+
+static const struct gdb_xml_element library_list_children[] =
+{
+ {
+ "library", library_attributes, NULL,
+ GDB_XML_EF_REPEATABLE | GDB_XML_EF_OPTIONAL,
+ library_list_start_library, NULL
+ },
+ { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
+static const struct gdb_xml_attribute library_list_attributes[] =
+{
+ { "version", GDB_XML_AF_NONE, NULL, NULL },
+ { "main-lm", GDB_XML_AF_OPTIONAL, gdb_xml_parse_attr_ulongest, NULL },
+ { NULL, GDB_XML_AF_NONE, NULL, NULL }
+};
+
+static const struct gdb_xml_element library_list_elements[] =
+{
+ { "library-list", library_list_attributes, library_list_children,
+ GDB_XML_EF_NONE, library_list_start_list, NULL },
+ { NULL, NULL, NULL, GDB_XML_EF_NONE, NULL, NULL }
+};
+
/* Implementation for target_so_ops.free_so. */

static void
@@ -940,6 +1038,69 @@ svr4_free_library_list (void *p_list)
}
}

+/* Parse qXfer:libraries:read packet into *SO_LIST_RETURN. Return 1 if
+
+ Return 0 if packet not supported, *SO_LIST_RETURN is not modified in such
+ case. Return 1 if *SO_LIST_RETURN contains the library list, it may be
+ empty, caller is responsible for freeing all its entries. */
+
+static int
+svr4_parse_libraries (const char *document, struct svr4_library_list *list)
+{
+ struct cleanup *back_to = make_cleanup (svr4_free_library_list,
+ &list->head);
+
+ memset (list, 0, sizeof (*list));
+ list->tailp = &list->head;
+ if (gdb_xml_parse_quick (_("target library list"), "library-list.dtd",
+ library_list_elements, document, list) == 0)
+ {
+ /* Parsed successfully, keep the result. */
+ discard_cleanups (back_to);
+ return 1;
+ }
+
+ do_cleanups (back_to);
+ return 0;
+}
+
+/* Attempt to get so_list from target via qXfer:libraries:read packet.
+
+ Return 0 if packet not supported, *SO_LIST_RETURN is not modified in such
+ case. Return 1 if *SO_LIST_RETURN contains the library list, it may be
+ empty, caller is responsible for freeing all its entries. */
+
+static int
+svr4_current_sos_via_xfer_libraries (struct svr4_library_list *list)
+{
+ char *library_document;
+ int result;
+ struct cleanup *back_to;
+
+ /* Fetch the list of shared libraries. */
+ library_document = target_read_stralloc (&current_target,
+ TARGET_OBJECT_LIBRARIES,
+ NULL);
+ if (library_document == NULL)
+ return 0;
+
+ back_to = make_cleanup (xfree, library_document);
+ result = svr4_parse_libraries (library_document, list);
+ do_cleanups (back_to);
+
+ return result;
+}
+
+#else
+
+static int
+svr4_current_sos_via_xfer_libraries (struct svr4_library_list *list)
+{
+ return 0;
+}
+
+#endif
+
/* If no shared library information is available from the dynamic
linker, build a fallback list from other sources. */

@@ -999,7 +1160,9 @@ svr4_read_so_list (CORE_ADDR lm, struct so_list ***link_ptr_ptr,

if (new->lm_info->l_prev != prev_lm)
{
- warning (_("Corrupted shared library list"));
+ warning (_("Corrupted shared library list: %s != %s"),
+ paddress (target_gdbarch, prev_lm),
+ paddress (target_gdbarch, new->lm_info->l_prev));
do_cleanups (old_chain);
break;
}
@@ -1060,6 +1223,18 @@ svr4_current_sos (void)
struct svr4_info *info;
struct cleanup *back_to;
int ignore_first;
+ struct svr4_library_list library_list;
+
+ if (svr4_current_sos_via_xfer_libraries (&library_list))
+ {
+ if (library_list.main_lm)
+ {
+ info = get_svr4_info ();
+ info->main_lm_addr = library_list.main_lm;
+ }
+
+ return library_list.head ? library_list.head : svr4_default_sos ();
+ }

info = get_svr4_info ();

--- a/gdb/testsuite/gdb.base/solib-corrupted.exp
+++ b/gdb/testsuite/gdb.base/solib-corrupted.exp
@@ -17,6 +17,12 @@ if {[skip_shlib_tests]} {
return 0
}

+if {[is_remote target]} {
+ # gdbserver prints the warning message but expect is parsing only the GDB
+ # output, not the gdbserver output.
+ return 0
+}
+
set testfile "solib-corrupted"
set srcfile start.c

@@ -47,4 +53,4 @@ gdb_test_multiple "p/x _r_debug->r_map->l_next = _r_debug->r_map" $test {
pass $test
}
}
-gdb_test "info sharedlibrary" "warning: Corrupted shared library list\r\n.*" "corrupted list"
+gdb_test "info sharedlibrary" "warning: Corrupted shared library list: .*" "corrupted list"