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

Jan Kratochvil
2011-08-09T09:07:11Z

On Tue, 09 Aug 2011 01:49:49 +0200, Paul Pluzhnikov wrote:
> --- gdbserver/linux-low.c 21 Jul 2011 23:46:12 -0000 1.173
> +++ gdbserver/linux-low.c 8 Aug 2011 23:34:10 -0000
> @@ -4755,6 +4755,289 @@ linux_emit_ops (void)
[...]
> +static CORE_ADDR
> +get_dynamic (const int pid, const int is_elf64)
> +{
> + CORE_ADDR phdr_memaddr;
> + int num_phdr, i;
> + unsigned char *phdr_buf;
> + const int phdr_size = is_elf64 ? sizeof (Elf64_Phdr) : sizeof (Elf32_Phdr);
> +
> +

Two empty lines, should be one.

> + 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;
> +
> + 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;

This does not work for -fPIE -pie executables such as those on Chromebook.
One needs to relocate A_VAL as it is the in-file (0-based) address while
in-memory address is shifted by X. X is calculated by solib-svr4.c
svr4_exec_displacement.

One can find here X probably most easily by subtracting PT_PHDR->P_VADDR from
PHDR_MEMADDR.

There is also a bug in it in solib-svr4.c scan_dyntag_auxv
[rfc] Use auxillary vector to retrieve .dynamic/.interp sections
http://sourceware.org/ml/gdb-patches/2008-08/msg00360.html
but one would need to patch the code around to get the fix applicable first.

> + }
> + else
> + {
> + Elf32_Phdr *const p = (Elf32_Phdr *) (phdr_buf + i * phdr_size);
> +
> + if (p->p_type == PT_DYNAMIC)
> + return p->p_vaddr;

Here too.

> + }
> + }
> +
> + 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);
> +}
> +
> +

Two empty lines, should be one.

> +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_next field in struct link_map. */
> + int l_next_offset;
> +
> + /* Offset to l_prev field in struct link_map. */
> + int l_prev_offset;
> +

Excessive empty line.

> + };
> +
> +/* Clear and refresh ALL_DLLS list. */
> +
> +static void
> +linux_refresh_libraries (void)
> +{
> + struct process_info_private *const priv = current_process ()->private;
> + char filename[PATH_MAX];
> + int pid, is_elf64, ptr_size, r_version;
> + CORE_ADDR lm_addr, lm_prev, l_name, l_addr, l_next, l_prev;
> +
> + 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. */
> + 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. */
> + 24, /* l_next offset in link_map. */
> + 32 /* l_prev offset in link_map. */
> + };
> + const struct link_map_offsets *lmo;
> +
> + 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;
> + ptr_size = is_elf64 ? 8 : 4;
> +
> + 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)
> + return;
> +
> + 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);
> + return;
> + }
> +
> + 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);
> + return;
> + }
> +
> + clear_all_dlls ();
> +
> + 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_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 ("corrupt solib chain: 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));
> + if (libname[0] != '\0')
> + loaded_dll ((const char *) libname, l_addr);

Unprotected against inferior string longer than PATH_MAX, this is a regression
against solib-svr4.c.

> +
> + if (l_next == 0)
> + break;
> +
> + lm_prev = lm_addr;
> + lm_addr = l_next;
> + }
> +
> + /* The library notification response is not expected for GNU/Linux. */
> + dlls_changed = 0;
> +}
> +
> +
> static struct target_ops linux_target_ops = {
> linux_create_inferior,
> linux_attach,
> @@ -4813,7 +5096,8 @@ static struct target_ops linux_target_op
> linux_cancel_breakpoints,
> linux_stabilize_threads,
> linux_install_fast_tracepoint_jump_pad,
> - linux_emit_ops
> + linux_emit_ops,
> + linux_refresh_libraries
> };
>
> static void
> Index: gdbserver/linux-low.h
> ===================================================================
> RCS file: /cvs/src/src/gdb/gdbserver/linux-low.h,v
> retrieving revision 1.47
> diff -u -p -r1.47 linux-low.h
> --- gdbserver/linux-low.h 1 Jan 2011 15:33:24 -0000 1.47
> +++ gdbserver/linux-low.h 8 Aug 2011 23:34:10 -0000
> @@ -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. -1 if not yet determined. 0 if no PT_DYNAMIC in Phdrs. */

+ /* &_r_debug. 0 if not yet determined. -1 if no PT_DYNAMIC in Phdrs. */

My mistake.

> + CORE_ADDR r_debug;
> };
>
> struct lwp_info;

But I still believe the GDB part should be more unified, I will try it.

Thanks,
Jan