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

Daniel Jacobowitz
2011-10-10T02:44:01Z

On Mon, Oct 3, 2011 at 5:55 PM, Jan Kratochvil
<jan.kratochvil@redhat.com> wrote:
> 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.

I would be a whole lot happier if we could use the same <library-list>
elements for SVR4. There may be some creative interpretations we can
use to make it work... yes, we'd likely have to pass the link map
addresses as additional information; fortunately that's easy. I'm not
sure I understand the issue with dealing with absolute library base
addresses. But in short, the way to think about this is to move all
the complicated bits of library processing to the target agent, and
give GDB a simplified view of what's really going on.

If that's not practical, then +1 to Pedro's suggestion; if you're
bulk-reading the link maps, or doing some svr4-specific semantics, we
should express it that way.

>
> 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"
>

--
Thanks,
Daniel