Message ID | 20190410190450.8612-1-adhemerval.zanella@linaro.org |
---|---|
State | New |
Headers | show |
Series | elf: Remove pldd (BZ#18035) | expand |
On 4/10/19 3:04 PM, Adhemerval Zanella wrote: > As reported in bugzilla itself and on man-pages [1] it has been > broken since 2.19. Also, its design tie glibc internal definition > by duplicate struct layouts and dynamic loader objects. I have a sustained objection to removing pldd. Have we looked into why it's broken? I'm happy to add a test-in-container runtime test for it so we can see breakage when it happens. It matches the same Solaris tool pldd, and is a useful utility. We probably haven't seen a problem because enterprise uses might have patched pldd to fix it, or RHEL7 which is on 2.17 still has a working version. > Also, as noted by man-pages the same behavior can be obtained with > > $ gdb -ex "set confirm off" -ex "set height 0" -ex "info shared" \ > -ex "quit" -p $pid | grep '^0x.*0x' That relies on a debugger on the target system and it might be a problem on production systems. > > Or with external tools as 'lsof'. An alternative way to show the > dynamic shared objects liked into a process on Linux would be to > use the /proc/<pid>/maps instead as following example: > > --- > > import sys, os > > def main(argv): > with open('/proc/' + argv[0] + '/maps', 'r') as maps: > segs = {} > st = os.stat('/proc/' + argv[0] + '/exe') > for line in maps: > fields = line.split() > if len(fields) < 5: > continue > major,minor = fields[3].split(':') > dev = os.makedev(int(major), int(minor)) > inode = int(fields[4]) > if dev == 0 or inode == 0: > continue > # Skip the map entry if the device + inode pair match that of the exe. > if dev == st.st_dev or inode == st.st_ino: > continue > > # Check if file is accessible. > try: > segst = os.stat(fields[5]) > except OSError: > continue > > # Print only executable segments. > if 'x' in fields[1]: > print(fields[5]) > > if __name__ == '__main__': > main(sys.argv[1:]) This requires python in base runtime, and might not be on a production container instance. > --- > > Checked on x86_64-linux-gnu. > > * elf/pldd-xx.c: Remove file. > * elf/pldd.c: Likewise. > * sysdeps/unix/sysv/linux/Makefile [$(subdir) == elf] (pldd): Remove > rule. What's wrong with fixing pldd? > [1] http://man7.org/linux/man-pages/man1/pldd.1.html > --- > elf/pldd-xx.c | 251 ---------------------- > elf/pldd.c | 344 ------------------------------- > sysdeps/unix/sysv/linux/Makefile | 4 - > 3 files changed, 599 deletions(-) > delete mode 100644 elf/pldd-xx.c > delete mode 100644 elf/pldd.c > > diff --git a/elf/pldd-xx.c b/elf/pldd-xx.c > deleted file mode 100644 > index 547f840ee1..0000000000 > --- a/elf/pldd-xx.c > +++ /dev/null > @@ -1,251 +0,0 @@ > -/* Copyright (C) 2011-2019 Free Software Foundation, Inc. > - This file is part of the GNU C Library. > - Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. > - > - The GNU C Library is free software; you can redistribute it and/or > - modify it under the terms of the GNU Lesser General Public > - License as published by the Free Software Foundation; either > - version 2.1 of the License, or (at your option) any later version. > - > - The GNU C Library 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 > - Lesser General Public License for more details. > - > - You should have received a copy of the GNU Lesser General Public > - License along with the GNU C Library; if not, see > - <http://www.gnu.org/licenses/>. */ > - > -#define E(name) E_(name, CLASS) > -#define E_(name, cl) E__(name, cl) > -#define E__(name, cl) name##cl > -#define EW(type) EW_(Elf, CLASS, type) > -#define EW_(e, w, t) EW__(e, w, _##t) > -#define EW__(e, w, t) e##w##t > - > -#define pldd_assert(name, exp) \ > - typedef int __assert_##name[((exp) != 0) - 1] > - > - > -struct E(link_map) > -{ > - EW(Addr) l_addr; > - EW(Addr) l_name; > - EW(Addr) l_ld; > - EW(Addr) l_next; > - EW(Addr) l_prev; > - EW(Addr) l_real; > - Lmid_t l_ns; > - EW(Addr) l_libname; > -}; > -#if CLASS == __ELF_NATIVE_CLASS > -pldd_assert (l_addr, (offsetof (struct link_map, l_addr) > - == offsetof (struct E(link_map), l_addr))); > -pldd_assert (l_name, (offsetof (struct link_map, l_name) > - == offsetof (struct E(link_map), l_name))); > -pldd_assert (l_next, (offsetof (struct link_map, l_next) > - == offsetof (struct E(link_map), l_next))); > -#endif > - > - > -struct E(libname_list) > -{ > - EW(Addr) name; > - EW(Addr) next; > -}; > -#if CLASS == __ELF_NATIVE_CLASS > -pldd_assert (name, (offsetof (struct libname_list, name) > - == offsetof (struct E(libname_list), name))); > -pldd_assert (next, (offsetof (struct libname_list, next) > - == offsetof (struct E(libname_list), next))); > -#endif > - > -struct E(r_debug) > -{ > - int r_version; > -#if CLASS == 64 > - int pad; > -#endif > - EW(Addr) r_map; > -}; > -#if CLASS == __ELF_NATIVE_CLASS > -pldd_assert (r_version, (offsetof (struct r_debug, r_version) > - == offsetof (struct E(r_debug), r_version))); > -pldd_assert (r_map, (offsetof (struct r_debug, r_map) > - == offsetof (struct E(r_debug), r_map))); > -#endif > - > - > -static int > - > -E(find_maps) (pid_t pid, void *auxv, size_t auxv_size) > -{ > - EW(Addr) phdr = 0; > - unsigned int phnum = 0; > - unsigned int phent = 0; > - > - EW(auxv_t) *auxvXX = (EW(auxv_t) *) auxv; > - for (int i = 0; i < auxv_size / sizeof (EW(auxv_t)); ++i) > - switch (auxvXX[i].a_type) > - { > - case AT_PHDR: > - phdr = auxvXX[i].a_un.a_val; > - break; > - case AT_PHNUM: > - phnum = auxvXX[i].a_un.a_val; > - break; > - case AT_PHENT: > - phent = auxvXX[i].a_un.a_val; > - break; > - default: > - break; > - } > - > - if (phdr == 0 || phnum == 0 || phent == 0) > - error (EXIT_FAILURE, 0, gettext ("cannot find program header of process")); > - > - EW(Phdr) *p = alloca (phnum * phent); > - if (pread64 (memfd, p, phnum * phent, phdr) != phnum * phent) > - { > - error (0, 0, gettext ("cannot read program header")); > - return EXIT_FAILURE; > - } > - > - /* Determine the load offset. We need this for interpreting the > - other program header entries so we do this in a separate loop. > - Fortunately it is the first time unless someone does something > - stupid when linking the application. */ > - EW(Addr) offset = 0; > - for (unsigned int i = 0; i < phnum; ++i) > - if (p[i].p_type == PT_PHDR) > - { > - offset = phdr - p[i].p_vaddr; > - break; > - } > - > - EW(Addr) list = 0; > - char *interp = NULL; > - for (unsigned int i = 0; i < phnum; ++i) > - if (p[i].p_type == PT_DYNAMIC) > - { > - EW(Dyn) *dyn = xmalloc (p[i].p_filesz); > - if (pread64 (memfd, dyn, p[i].p_filesz, offset + p[i].p_vaddr) > - != p[i].p_filesz) > - { > - error (0, 0, gettext ("cannot read dynamic section")); > - return EXIT_FAILURE; > - } > - > - /* Search for the DT_DEBUG entry. */ > - for (unsigned int j = 0; j < p[i].p_filesz / sizeof (EW(Dyn)); ++j) > - if (dyn[j].d_tag == DT_DEBUG && dyn[j].d_un.d_ptr != 0) > - { > - struct E(r_debug) r; > - if (pread64 (memfd, &r, sizeof (r), dyn[j].d_un.d_ptr) > - != sizeof (r)) > - { > - error (0, 0, gettext ("cannot read r_debug")); > - return EXIT_FAILURE; > - } > - > - if (r.r_map != 0) > - { > - list = r.r_map; > - break; > - } > - } > - > - free (dyn); > - break; > - } > - else if (p[i].p_type == PT_INTERP) > - { > - interp = alloca (p[i].p_filesz); > - if (pread64 (memfd, interp, p[i].p_filesz, offset + p[i].p_vaddr) > - != p[i].p_filesz) > - { > - error (0, 0, gettext ("cannot read program interpreter")); > - return EXIT_FAILURE; > - } > - } > - > - if (list == 0) > - { > - if (interp == NULL) > - { > - // XXX check whether the executable itself is the loader > - return EXIT_FAILURE; > - } > - > - // XXX perhaps try finding ld.so and _r_debug in it > - > - return EXIT_FAILURE; > - } > - > - /* Print the PID and program name first. */ > - printf ("%lu:\t%s\n", (unsigned long int) pid, exe); > - > - /* Iterate over the list of objects and print the information. */ > - struct scratch_buffer tmpbuf; > - scratch_buffer_init (&tmpbuf); > - int status = 0; > - do > - { > - struct E(link_map) m; > - if (pread64 (memfd, &m, sizeof (m), list) != sizeof (m)) > - { > - error (0, 0, gettext ("cannot read link map")); > - status = EXIT_FAILURE; > - goto out; > - } > - > - EW(Addr) name_offset = m.l_name; > - again: > - while (1) > - { > - ssize_t n = pread64 (memfd, tmpbuf.data, tmpbuf.length, name_offset); > - if (n == -1) > - { > - error (0, 0, gettext ("cannot read object name")); > - status = EXIT_FAILURE; > - goto out; > - } > - > - if (memchr (tmpbuf.data, '\0', n) != NULL) > - break; > - > - if (!scratch_buffer_grow (&tmpbuf)) > - { > - error (0, 0, gettext ("cannot allocate buffer for object name")); > - status = EXIT_FAILURE; > - goto out; > - } > - } > - > - if (((char *)tmpbuf.data)[0] == '\0' && name_offset == m.l_name > - && m.l_libname != 0) > - { > - /* Try the l_libname element. */ > - struct E(libname_list) ln; > - if (pread64 (memfd, &ln, sizeof (ln), m.l_libname) == sizeof (ln)) > - { > - name_offset = ln.name; > - goto again; > - } > - } > - > - /* Skip over the executable. */ > - if (((char *)tmpbuf.data)[0] != '\0') > - printf ("%s\n", (char *)tmpbuf.data); > - > - list = m.l_next; > - } > - while (list != 0); > - > - out: > - scratch_buffer_free (&tmpbuf); > - return status; > -} > - > - > -#undef CLASS > diff --git a/elf/pldd.c b/elf/pldd.c > deleted file mode 100644 > index f3fac4e487..0000000000 > --- a/elf/pldd.c > +++ /dev/null > @@ -1,344 +0,0 @@ > -/* List dynamic shared objects linked into given process. > - Copyright (C) 2011-2019 Free Software Foundation, Inc. > - This file is part of the GNU C Library. > - Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. > - > - The GNU C Library is free software; you can redistribute it and/or > - modify it under the terms of the GNU Lesser General Public > - License as published by the Free Software Foundation; either > - version 2.1 of the License, or (at your option) any later version. > - > - The GNU C Library 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 > - Lesser General Public License for more details. > - > - You should have received a copy of the GNU Lesser General Public > - License along with the GNU C Library; if not, see > - <http://www.gnu.org/licenses/>. */ > - > -#include <alloca.h> > -#include <argp.h> > -#include <assert.h> > -#include <dirent.h> > -#include <elf.h> > -#include <errno.h> > -#include <error.h> > -#include <fcntl.h> > -#include <libintl.h> > -#include <link.h> > -#include <stddef.h> > -#include <stdio.h> > -#include <stdlib.h> > -#include <string.h> > -#include <unistd.h> > -#include <sys/ptrace.h> > -#include <sys/stat.h> > -#include <sys/wait.h> > -#include <scratch_buffer.h> > - > -#include <ldsodefs.h> > -#include <version.h> > - > -/* Global variables. */ > -extern char *program_invocation_short_name; > -#define PACKAGE _libc_intl_domainname > - > -/* External functions. */ > -#include <programs/xmalloc.h> > - > -/* Name and version of program. */ > -static void print_version (FILE *stream, struct argp_state *state); > -void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; > - > -/* Function to print some extra text in the help message. */ > -static char *more_help (int key, const char *text, void *input); > - > -/* Definitions of arguments for argp functions. */ > -static const struct argp_option options[] = > -{ > - { NULL, 0, NULL, 0, NULL } > -}; > - > -/* Short description of program. */ > -static const char doc[] = N_("\ > -List dynamic shared objects loaded into process."); > - > -/* Strings for arguments in help texts. */ > -static const char args_doc[] = N_("PID"); > - > -/* Prototype for option handler. */ > -static error_t parse_opt (int key, char *arg, struct argp_state *state); > - > -/* Data structure to communicate with argp functions. */ > -static struct argp argp = > -{ > - options, parse_opt, args_doc, doc, NULL, more_help, NULL > -}; > - > -// File descriptor of /proc/*/mem file. > -static int memfd; > - > -/* Name of the executable */ > -static char *exe; > - > -/* Local functions. */ > -static int get_process_info (int dfd, long int pid); > -static void wait_for_ptrace_stop (long int pid); > - > - > -int > -main (int argc, char *argv[]) > -{ > - /* Parse and process arguments. */ > - int remaining; > - argp_parse (&argp, argc, argv, 0, &remaining, NULL); > - > - if (remaining != argc - 1) > - { > - fprintf (stderr, > - gettext ("Exactly one parameter with process ID required.\n")); > - argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name); > - return 1; > - } > - > - assert (sizeof (pid_t) == sizeof (int) > - || sizeof (pid_t) == sizeof (long int)); > - char *endp; > - errno = 0; > - long int pid = strtol (argv[remaining], &endp, 10); > - if (pid < 0 || (pid == ULONG_MAX && errno == ERANGE) || *endp != '\0' > - || (sizeof (pid_t) < sizeof (pid) && pid > INT_MAX)) > - error (EXIT_FAILURE, 0, gettext ("invalid process ID '%s'"), > - argv[remaining]); > - > - /* Determine the program name. */ > - char buf[7 + 3 * sizeof (pid)]; > - snprintf (buf, sizeof (buf), "/proc/%lu", pid); > - int dfd = open (buf, O_RDONLY | O_DIRECTORY); > - if (dfd == -1) > - error (EXIT_FAILURE, errno, gettext ("cannot open %s"), buf); > - > - struct scratch_buffer exebuf; > - scratch_buffer_init (&exebuf); > - ssize_t nexe; > - while ((nexe = readlinkat (dfd, "exe", > - exebuf.data, exebuf.length)) == exebuf.length) > - { > - if (!scratch_buffer_grow (&exebuf)) > - { > - nexe = -1; > - break; > - } > - } > - if (nexe == -1) > - exe = (char *) "<program name undetermined>"; > - else > - { > - exe = exebuf.data; > - exe[nexe] = '\0'; > - } > - > - /* Stop all threads since otherwise the list of loaded modules might > - change while we are reading it. */ > - struct thread_list > - { > - pid_t tid; > - struct thread_list *next; > - } *thread_list = NULL; > - > - int taskfd = openat (dfd, "task", O_RDONLY | O_DIRECTORY | O_CLOEXEC); > - if (taskfd == 1) > - error (EXIT_FAILURE, errno, gettext ("cannot open %s/task"), buf); > - DIR *dir = fdopendir (taskfd); > - if (dir == NULL) > - error (EXIT_FAILURE, errno, gettext ("cannot prepare reading %s/task"), > - buf); > - > - struct dirent64 *d; > - while ((d = readdir64 (dir)) != NULL) > - { > - if (! isdigit (d->d_name[0])) > - continue; > - > - errno = 0; > - long int tid = strtol (d->d_name, &endp, 10); > - if (tid < 0 || (tid == ULONG_MAX && errno == ERANGE) || *endp != '\0' > - || (sizeof (pid_t) < sizeof (pid) && tid > INT_MAX)) > - error (EXIT_FAILURE, 0, gettext ("invalid thread ID '%s'"), > - d->d_name); > - > - if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0) > - { > - /* There might be a race between reading the directory and > - threads terminating. Ignore errors attaching to unknown > - threads unless this is the main thread. */ > - if (errno == ESRCH && tid != pid) > - continue; > - > - error (EXIT_FAILURE, errno, gettext ("cannot attach to process %lu"), > - tid); > - } > - > - wait_for_ptrace_stop (tid); > - > - struct thread_list *newp = alloca (sizeof (*newp)); > - newp->tid = tid; > - newp->next = thread_list; > - thread_list = newp; > - } > - > - closedir (dir); > - > - int status = get_process_info (dfd, pid); > - > - assert (thread_list != NULL); > - do > - { > - ptrace (PTRACE_DETACH, thread_list->tid, NULL, NULL); > - thread_list = thread_list->next; > - } > - while (thread_list != NULL); > - > - close (dfd); > - > - return status; > -} > - > - > -/* Wait for PID to enter ptrace-stop state after being attached. */ > -static void > -wait_for_ptrace_stop (long int pid) > -{ > - int status; > - > - /* While waiting for SIGSTOP being delivered to the tracee we have to > - reinject any other pending signal. Ignore all other errors. */ > - while (waitpid (pid, &status, __WALL) == pid && WIFSTOPPED (status)) > - { > - /* The STOP signal should not be delivered to the tracee. */ > - if (WSTOPSIG (status) == SIGSTOP) > - return; > - if (ptrace (PTRACE_CONT, pid, NULL, > - (void *) (uintptr_t) WSTOPSIG (status))) > - /* The only possible error is that the process died. */ > - return; > - } > -} > - > - > -/* Handle program arguments. */ > -static error_t > -parse_opt (int key, char *arg, struct argp_state *state) > -{ > - switch (key) > - { > - default: > - return ARGP_ERR_UNKNOWN; > - } > - return 0; > -} > - > - > -/* Print bug-reporting information in the help message. */ > -static char * > -more_help (int key, const char *text, void *input) > -{ > - char *tp = NULL; > - switch (key) > - { > - case ARGP_KEY_HELP_EXTRA: > - /* We print some extra information. */ > - if (asprintf (&tp, gettext ("\ > -For bug reporting instructions, please see:\n\ > -%s.\n"), REPORT_BUGS_TO) < 0) > - return NULL; > - return tp; > - default: > - break; > - } > - return (char *) text; > -} > - > -/* Print the version information. */ > -static void > -print_version (FILE *stream, struct argp_state *state) > -{ > - fprintf (stream, "pldd %s%s\n", PKGVERSION, VERSION); > - fprintf (stream, gettext ("\ > -Copyright (C) %s Free Software Foundation, Inc.\n\ > -This is free software; see the source for copying conditions. There is NO\n\ > -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ > -"), "2019"); > - fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); > -} > - > - > -#define CLASS 32 > -#include "pldd-xx.c" > -#define CLASS 64 > -#include "pldd-xx.c" > - > - > -static int > -get_process_info (int dfd, long int pid) > -{ > - memfd = openat (dfd, "mem", O_RDONLY); > - if (memfd == -1) > - goto no_info; > - > - int fd = openat (dfd, "exe", O_RDONLY); > - if (fd == -1) > - { > - no_info: > - error (0, errno, gettext ("cannot get information about process %lu"), > - pid); > - return EXIT_FAILURE; > - } > - > - char e_ident[EI_NIDENT]; > - if (read (fd, e_ident, EI_NIDENT) != EI_NIDENT) > - goto no_info; > - > - close (fd); > - > - if (memcmp (e_ident, ELFMAG, SELFMAG) != 0) > - { > - error (0, 0, gettext ("process %lu is no ELF program"), pid); > - return EXIT_FAILURE; > - } > - > - fd = openat (dfd, "auxv", O_RDONLY); > - if (fd == -1) > - goto no_info; > - > - size_t auxv_size = 0; > - void *auxv = NULL; > - while (1) > - { > - auxv_size += 512; > - auxv = xrealloc (auxv, auxv_size); > - > - ssize_t n = pread (fd, auxv, auxv_size, 0); > - if (n < 0) > - goto no_info; > - if (n < auxv_size) > - { > - auxv_size = n; > - break; > - } > - } > - > - close (fd); > - > - int retval; > - if (e_ident[EI_CLASS] == ELFCLASS32) > - retval = find_maps32 (pid, auxv, auxv_size); > - else > - retval = find_maps64 (pid, auxv, auxv_size); > - > - free (auxv); > - close (memfd); > - > - return retval; > -} > diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile > index 52ac6ad484..e799473b17 100644 > --- a/sysdeps/unix/sysv/linux/Makefile > +++ b/sysdeps/unix/sysv/linux/Makefile > @@ -212,10 +212,6 @@ sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \ > dl-fxstatat64 > > libof-lddlibc4 = lddlibc4 > - > -others += pldd > -install-bin += pldd > -$(objpfx)pldd: $(objpfx)xmalloc.o > endif > > ifeq ($(subdir),rt) > -- Cheers, Carlos.
On 10/04/2019 17:17, Carlos O'Donell wrote: > On 4/10/19 3:04 PM, Adhemerval Zanella wrote: >> As reported in bugzilla itself and on man-pages [1] it has been >> broken since 2.19. Also, its design tie glibc internal definition >> by duplicate struct layouts and dynamic loader objects. > > I have a sustained objection to removing pldd. > > Have we looked into why it's broken? I will take a second look, but my wildly guess is most likely some internal linker interface has drift from pldd internal definitions and when it tries to poke on process memory it reads invalid memory. > > I'm happy to add a test-in-container runtime test for it so we can > see breakage when it happens. > > It matches the same Solaris tool pldd, and is a useful utility. I agree it is useful, but the same functionally can be provided with different tools and/or kernel facilities. The advantage I see that pldd might have it is suppose to poke on internal glibc data structures and get the loaded dynamic shared objects without relying on additional kernel information. > > We probably haven't seen a problem because enterprise uses might have > patched pldd to fix it, or RHEL7 which is on 2.17 still has a working > version. The Ubuntu version I have installed all show the same issue (14.04, 16.04, and 18.04). > >> Also, as noted by man-pages the same behavior can be obtained with >> >> $ gdb -ex "set confirm off" -ex "set height 0" -ex "info shared" \ >> -ex "quit" -p $pid | grep '^0x.*0x' > > That relies on a debugger on the target system and it might be a problem > on production systems. This is just an example on how to obtain the same information. > >> >> Or with external tools as 'lsof'. An alternative way to show the >> dynamic shared objects liked into a process on Linux would be to >> use the /proc/<pid>/maps instead as following example: >> >> --- >> >> import sys, os >> >> def main(argv): >> with open('/proc/' + argv[0] + '/maps', 'r') as maps: >> segs = {} >> st = os.stat('/proc/' + argv[0] + '/exe') >> for line in maps: >> fields = line.split() >> if len(fields) < 5: >> continue >> major,minor = fields[3].split(':') >> dev = os.makedev(int(major), int(minor)) >> inode = int(fields[4]) >> if dev == 0 or inode == 0: >> continue >> # Skip the map entry if the device + inode pair match that of the exe. >> if dev == st.st_dev or inode == st.st_ino: >> continue >> >> # Check if file is accessible. >> try: >> segst = os.stat(fields[5]) >> except OSError: >> continue >> >> # Print only executable segments. >> if 'x' in fields[1]: >> print(fields[5]) >> >> if __name__ == '__main__': >> main(sys.argv[1:]) > > This requires python in base runtime, and might not be on a production > container instance. Ok, the question I have is whether we want to make pldd still poke the loader internal data structures directly using /proc/<pid>/mem plus ptrace or if we can use a simplified version by parsing /proc/<pid>/maps (as lsof does). For latter, why we can't use use lsof instead? Do we really need to provide a similar tool (but will less features)? > >> --- >> >> Checked on x86_64-linux-gnu. >> >> * elf/pldd-xx.c: Remove file. >> * elf/pldd.c: Likewise. >> * sysdeps/unix/sysv/linux/Makefile [$(subdir) == elf] (pldd): Remove >> rule. > > What's wrong with fixing pldd? > >> [1] http://man7.org/linux/man-pages/man1/pldd.1.html >> --- >> elf/pldd-xx.c | 251 ---------------------- >> elf/pldd.c | 344 ------------------------------- >> sysdeps/unix/sysv/linux/Makefile | 4 - >> 3 files changed, 599 deletions(-) >> delete mode 100644 elf/pldd-xx.c >> delete mode 100644 elf/pldd.c >> >> diff --git a/elf/pldd-xx.c b/elf/pldd-xx.c >> deleted file mode 100644 >> index 547f840ee1..0000000000 >> --- a/elf/pldd-xx.c >> +++ /dev/null >> @@ -1,251 +0,0 @@ >> -/* Copyright (C) 2011-2019 Free Software Foundation, Inc. >> - This file is part of the GNU C Library. >> - Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. >> - >> - The GNU C Library is free software; you can redistribute it and/or >> - modify it under the terms of the GNU Lesser General Public >> - License as published by the Free Software Foundation; either >> - version 2.1 of the License, or (at your option) any later version. >> - >> - The GNU C Library 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 >> - Lesser General Public License for more details. >> - >> - You should have received a copy of the GNU Lesser General Public >> - License along with the GNU C Library; if not, see >> - <http://www.gnu.org/licenses/>. */ >> - >> -#define E(name) E_(name, CLASS) >> -#define E_(name, cl) E__(name, cl) >> -#define E__(name, cl) name##cl >> -#define EW(type) EW_(Elf, CLASS, type) >> -#define EW_(e, w, t) EW__(e, w, _##t) >> -#define EW__(e, w, t) e##w##t >> - >> -#define pldd_assert(name, exp) \ >> - typedef int __assert_##name[((exp) != 0) - 1] >> - >> - >> -struct E(link_map) >> -{ >> - EW(Addr) l_addr; >> - EW(Addr) l_name; >> - EW(Addr) l_ld; >> - EW(Addr) l_next; >> - EW(Addr) l_prev; >> - EW(Addr) l_real; >> - Lmid_t l_ns; >> - EW(Addr) l_libname; >> -}; >> -#if CLASS == __ELF_NATIVE_CLASS >> -pldd_assert (l_addr, (offsetof (struct link_map, l_addr) >> - == offsetof (struct E(link_map), l_addr))); >> -pldd_assert (l_name, (offsetof (struct link_map, l_name) >> - == offsetof (struct E(link_map), l_name))); >> -pldd_assert (l_next, (offsetof (struct link_map, l_next) >> - == offsetof (struct E(link_map), l_next))); >> -#endif >> - >> - >> -struct E(libname_list) >> -{ >> - EW(Addr) name; >> - EW(Addr) next; >> -}; >> -#if CLASS == __ELF_NATIVE_CLASS >> -pldd_assert (name, (offsetof (struct libname_list, name) >> - == offsetof (struct E(libname_list), name))); >> -pldd_assert (next, (offsetof (struct libname_list, next) >> - == offsetof (struct E(libname_list), next))); >> -#endif >> - >> -struct E(r_debug) >> -{ >> - int r_version; >> -#if CLASS == 64 >> - int pad; >> -#endif >> - EW(Addr) r_map; >> -}; >> -#if CLASS == __ELF_NATIVE_CLASS >> -pldd_assert (r_version, (offsetof (struct r_debug, r_version) >> - == offsetof (struct E(r_debug), r_version))); >> -pldd_assert (r_map, (offsetof (struct r_debug, r_map) >> - == offsetof (struct E(r_debug), r_map))); >> -#endif >> - >> - >> -static int >> - >> -E(find_maps) (pid_t pid, void *auxv, size_t auxv_size) >> -{ >> - EW(Addr) phdr = 0; >> - unsigned int phnum = 0; >> - unsigned int phent = 0; >> - >> - EW(auxv_t) *auxvXX = (EW(auxv_t) *) auxv; >> - for (int i = 0; i < auxv_size / sizeof (EW(auxv_t)); ++i) >> - switch (auxvXX[i].a_type) >> - { >> - case AT_PHDR: >> - phdr = auxvXX[i].a_un.a_val; >> - break; >> - case AT_PHNUM: >> - phnum = auxvXX[i].a_un.a_val; >> - break; >> - case AT_PHENT: >> - phent = auxvXX[i].a_un.a_val; >> - break; >> - default: >> - break; >> - } >> - >> - if (phdr == 0 || phnum == 0 || phent == 0) >> - error (EXIT_FAILURE, 0, gettext ("cannot find program header of process")); >> - >> - EW(Phdr) *p = alloca (phnum * phent); >> - if (pread64 (memfd, p, phnum * phent, phdr) != phnum * phent) >> - { >> - error (0, 0, gettext ("cannot read program header")); >> - return EXIT_FAILURE; >> - } >> - >> - /* Determine the load offset. We need this for interpreting the >> - other program header entries so we do this in a separate loop. >> - Fortunately it is the first time unless someone does something >> - stupid when linking the application. */ >> - EW(Addr) offset = 0; >> - for (unsigned int i = 0; i < phnum; ++i) >> - if (p[i].p_type == PT_PHDR) >> - { >> - offset = phdr - p[i].p_vaddr; >> - break; >> - } >> - >> - EW(Addr) list = 0; >> - char *interp = NULL; >> - for (unsigned int i = 0; i < phnum; ++i) >> - if (p[i].p_type == PT_DYNAMIC) >> - { >> - EW(Dyn) *dyn = xmalloc (p[i].p_filesz); >> - if (pread64 (memfd, dyn, p[i].p_filesz, offset + p[i].p_vaddr) >> - != p[i].p_filesz) >> - { >> - error (0, 0, gettext ("cannot read dynamic section")); >> - return EXIT_FAILURE; >> - } >> - >> - /* Search for the DT_DEBUG entry. */ >> - for (unsigned int j = 0; j < p[i].p_filesz / sizeof (EW(Dyn)); ++j) >> - if (dyn[j].d_tag == DT_DEBUG && dyn[j].d_un.d_ptr != 0) >> - { >> - struct E(r_debug) r; >> - if (pread64 (memfd, &r, sizeof (r), dyn[j].d_un.d_ptr) >> - != sizeof (r)) >> - { >> - error (0, 0, gettext ("cannot read r_debug")); >> - return EXIT_FAILURE; >> - } >> - >> - if (r.r_map != 0) >> - { >> - list = r.r_map; >> - break; >> - } >> - } >> - >> - free (dyn); >> - break; >> - } >> - else if (p[i].p_type == PT_INTERP) >> - { >> - interp = alloca (p[i].p_filesz); >> - if (pread64 (memfd, interp, p[i].p_filesz, offset + p[i].p_vaddr) >> - != p[i].p_filesz) >> - { >> - error (0, 0, gettext ("cannot read program interpreter")); >> - return EXIT_FAILURE; >> - } >> - } >> - >> - if (list == 0) >> - { >> - if (interp == NULL) >> - { >> - // XXX check whether the executable itself is the loader >> - return EXIT_FAILURE; >> - } >> - >> - // XXX perhaps try finding ld.so and _r_debug in it >> - >> - return EXIT_FAILURE; >> - } >> - >> - /* Print the PID and program name first. */ >> - printf ("%lu:\t%s\n", (unsigned long int) pid, exe); >> - >> - /* Iterate over the list of objects and print the information. */ >> - struct scratch_buffer tmpbuf; >> - scratch_buffer_init (&tmpbuf); >> - int status = 0; >> - do >> - { >> - struct E(link_map) m; >> - if (pread64 (memfd, &m, sizeof (m), list) != sizeof (m)) >> - { >> - error (0, 0, gettext ("cannot read link map")); >> - status = EXIT_FAILURE; >> - goto out; >> - } >> - >> - EW(Addr) name_offset = m.l_name; >> - again: >> - while (1) >> - { >> - ssize_t n = pread64 (memfd, tmpbuf.data, tmpbuf.length, name_offset); >> - if (n == -1) >> - { >> - error (0, 0, gettext ("cannot read object name")); >> - status = EXIT_FAILURE; >> - goto out; >> - } >> - >> - if (memchr (tmpbuf.data, '\0', n) != NULL) >> - break; >> - >> - if (!scratch_buffer_grow (&tmpbuf)) >> - { >> - error (0, 0, gettext ("cannot allocate buffer for object name")); >> - status = EXIT_FAILURE; >> - goto out; >> - } >> - } >> - >> - if (((char *)tmpbuf.data)[0] == '\0' && name_offset == m.l_name >> - && m.l_libname != 0) >> - { >> - /* Try the l_libname element. */ >> - struct E(libname_list) ln; >> - if (pread64 (memfd, &ln, sizeof (ln), m.l_libname) == sizeof (ln)) >> - { >> - name_offset = ln.name; >> - goto again; >> - } >> - } >> - >> - /* Skip over the executable. */ >> - if (((char *)tmpbuf.data)[0] != '\0') >> - printf ("%s\n", (char *)tmpbuf.data); >> - >> - list = m.l_next; >> - } >> - while (list != 0); >> - >> - out: >> - scratch_buffer_free (&tmpbuf); >> - return status; >> -} >> - >> - >> -#undef CLASS >> diff --git a/elf/pldd.c b/elf/pldd.c >> deleted file mode 100644 >> index f3fac4e487..0000000000 >> --- a/elf/pldd.c >> +++ /dev/null >> @@ -1,344 +0,0 @@ >> -/* List dynamic shared objects linked into given process. >> - Copyright (C) 2011-2019 Free Software Foundation, Inc. >> - This file is part of the GNU C Library. >> - Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. >> - >> - The GNU C Library is free software; you can redistribute it and/or >> - modify it under the terms of the GNU Lesser General Public >> - License as published by the Free Software Foundation; either >> - version 2.1 of the License, or (at your option) any later version. >> - >> - The GNU C Library 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 >> - Lesser General Public License for more details. >> - >> - You should have received a copy of the GNU Lesser General Public >> - License along with the GNU C Library; if not, see >> - <http://www.gnu.org/licenses/>. */ >> - >> -#include <alloca.h> >> -#include <argp.h> >> -#include <assert.h> >> -#include <dirent.h> >> -#include <elf.h> >> -#include <errno.h> >> -#include <error.h> >> -#include <fcntl.h> >> -#include <libintl.h> >> -#include <link.h> >> -#include <stddef.h> >> -#include <stdio.h> >> -#include <stdlib.h> >> -#include <string.h> >> -#include <unistd.h> >> -#include <sys/ptrace.h> >> -#include <sys/stat.h> >> -#include <sys/wait.h> >> -#include <scratch_buffer.h> >> - >> -#include <ldsodefs.h> >> -#include <version.h> >> - >> -/* Global variables. */ >> -extern char *program_invocation_short_name; >> -#define PACKAGE _libc_intl_domainname >> - >> -/* External functions. */ >> -#include <programs/xmalloc.h> >> - >> -/* Name and version of program. */ >> -static void print_version (FILE *stream, struct argp_state *state); >> -void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; >> - >> -/* Function to print some extra text in the help message. */ >> -static char *more_help (int key, const char *text, void *input); >> - >> -/* Definitions of arguments for argp functions. */ >> -static const struct argp_option options[] = >> -{ >> - { NULL, 0, NULL, 0, NULL } >> -}; >> - >> -/* Short description of program. */ >> -static const char doc[] = N_("\ >> -List dynamic shared objects loaded into process."); >> - >> -/* Strings for arguments in help texts. */ >> -static const char args_doc[] = N_("PID"); >> - >> -/* Prototype for option handler. */ >> -static error_t parse_opt (int key, char *arg, struct argp_state *state); >> - >> -/* Data structure to communicate with argp functions. */ >> -static struct argp argp = >> -{ >> - options, parse_opt, args_doc, doc, NULL, more_help, NULL >> -}; >> - >> -// File descriptor of /proc/*/mem file. >> -static int memfd; >> - >> -/* Name of the executable */ >> -static char *exe; >> - >> -/* Local functions. */ >> -static int get_process_info (int dfd, long int pid); >> -static void wait_for_ptrace_stop (long int pid); >> - >> - >> -int >> -main (int argc, char *argv[]) >> -{ >> - /* Parse and process arguments. */ >> - int remaining; >> - argp_parse (&argp, argc, argv, 0, &remaining, NULL); >> - >> - if (remaining != argc - 1) >> - { >> - fprintf (stderr, >> - gettext ("Exactly one parameter with process ID required.\n")); >> - argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name); >> - return 1; >> - } >> - >> - assert (sizeof (pid_t) == sizeof (int) >> - || sizeof (pid_t) == sizeof (long int)); >> - char *endp; >> - errno = 0; >> - long int pid = strtol (argv[remaining], &endp, 10); >> - if (pid < 0 || (pid == ULONG_MAX && errno == ERANGE) || *endp != '\0' >> - || (sizeof (pid_t) < sizeof (pid) && pid > INT_MAX)) >> - error (EXIT_FAILURE, 0, gettext ("invalid process ID '%s'"), >> - argv[remaining]); >> - >> - /* Determine the program name. */ >> - char buf[7 + 3 * sizeof (pid)]; >> - snprintf (buf, sizeof (buf), "/proc/%lu", pid); >> - int dfd = open (buf, O_RDONLY | O_DIRECTORY); >> - if (dfd == -1) >> - error (EXIT_FAILURE, errno, gettext ("cannot open %s"), buf); >> - >> - struct scratch_buffer exebuf; >> - scratch_buffer_init (&exebuf); >> - ssize_t nexe; >> - while ((nexe = readlinkat (dfd, "exe", >> - exebuf.data, exebuf.length)) == exebuf.length) >> - { >> - if (!scratch_buffer_grow (&exebuf)) >> - { >> - nexe = -1; >> - break; >> - } >> - } >> - if (nexe == -1) >> - exe = (char *) "<program name undetermined>"; >> - else >> - { >> - exe = exebuf.data; >> - exe[nexe] = '\0'; >> - } >> - >> - /* Stop all threads since otherwise the list of loaded modules might >> - change while we are reading it. */ >> - struct thread_list >> - { >> - pid_t tid; >> - struct thread_list *next; >> - } *thread_list = NULL; >> - >> - int taskfd = openat (dfd, "task", O_RDONLY | O_DIRECTORY | O_CLOEXEC); >> - if (taskfd == 1) >> - error (EXIT_FAILURE, errno, gettext ("cannot open %s/task"), buf); >> - DIR *dir = fdopendir (taskfd); >> - if (dir == NULL) >> - error (EXIT_FAILURE, errno, gettext ("cannot prepare reading %s/task"), >> - buf); >> - >> - struct dirent64 *d; >> - while ((d = readdir64 (dir)) != NULL) >> - { >> - if (! isdigit (d->d_name[0])) >> - continue; >> - >> - errno = 0; >> - long int tid = strtol (d->d_name, &endp, 10); >> - if (tid < 0 || (tid == ULONG_MAX && errno == ERANGE) || *endp != '\0' >> - || (sizeof (pid_t) < sizeof (pid) && tid > INT_MAX)) >> - error (EXIT_FAILURE, 0, gettext ("invalid thread ID '%s'"), >> - d->d_name); >> - >> - if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0) >> - { >> - /* There might be a race between reading the directory and >> - threads terminating. Ignore errors attaching to unknown >> - threads unless this is the main thread. */ >> - if (errno == ESRCH && tid != pid) >> - continue; >> - >> - error (EXIT_FAILURE, errno, gettext ("cannot attach to process %lu"), >> - tid); >> - } >> - >> - wait_for_ptrace_stop (tid); >> - >> - struct thread_list *newp = alloca (sizeof (*newp)); >> - newp->tid = tid; >> - newp->next = thread_list; >> - thread_list = newp; >> - } >> - >> - closedir (dir); >> - >> - int status = get_process_info (dfd, pid); >> - >> - assert (thread_list != NULL); >> - do >> - { >> - ptrace (PTRACE_DETACH, thread_list->tid, NULL, NULL); >> - thread_list = thread_list->next; >> - } >> - while (thread_list != NULL); >> - >> - close (dfd); >> - >> - return status; >> -} >> - >> - >> -/* Wait for PID to enter ptrace-stop state after being attached. */ >> -static void >> -wait_for_ptrace_stop (long int pid) >> -{ >> - int status; >> - >> - /* While waiting for SIGSTOP being delivered to the tracee we have to >> - reinject any other pending signal. Ignore all other errors. */ >> - while (waitpid (pid, &status, __WALL) == pid && WIFSTOPPED (status)) >> - { >> - /* The STOP signal should not be delivered to the tracee. */ >> - if (WSTOPSIG (status) == SIGSTOP) >> - return; >> - if (ptrace (PTRACE_CONT, pid, NULL, >> - (void *) (uintptr_t) WSTOPSIG (status))) >> - /* The only possible error is that the process died. */ >> - return; >> - } >> -} >> - >> - >> -/* Handle program arguments. */ >> -static error_t >> -parse_opt (int key, char *arg, struct argp_state *state) >> -{ >> - switch (key) >> - { >> - default: >> - return ARGP_ERR_UNKNOWN; >> - } >> - return 0; >> -} >> - >> - >> -/* Print bug-reporting information in the help message. */ >> -static char * >> -more_help (int key, const char *text, void *input) >> -{ >> - char *tp = NULL; >> - switch (key) >> - { >> - case ARGP_KEY_HELP_EXTRA: >> - /* We print some extra information. */ >> - if (asprintf (&tp, gettext ("\ >> -For bug reporting instructions, please see:\n\ >> -%s.\n"), REPORT_BUGS_TO) < 0) >> - return NULL; >> - return tp; >> - default: >> - break; >> - } >> - return (char *) text; >> -} >> - >> -/* Print the version information. */ >> -static void >> -print_version (FILE *stream, struct argp_state *state) >> -{ >> - fprintf (stream, "pldd %s%s\n", PKGVERSION, VERSION); >> - fprintf (stream, gettext ("\ >> -Copyright (C) %s Free Software Foundation, Inc.\n\ >> -This is free software; see the source for copying conditions. There is NO\n\ >> -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ >> -"), "2019"); >> - fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); >> -} >> - >> - >> -#define CLASS 32 >> -#include "pldd-xx.c" >> -#define CLASS 64 >> -#include "pldd-xx.c" >> - >> - >> -static int >> -get_process_info (int dfd, long int pid) >> -{ >> - memfd = openat (dfd, "mem", O_RDONLY); >> - if (memfd == -1) >> - goto no_info; >> - >> - int fd = openat (dfd, "exe", O_RDONLY); >> - if (fd == -1) >> - { >> - no_info: >> - error (0, errno, gettext ("cannot get information about process %lu"), >> - pid); >> - return EXIT_FAILURE; >> - } >> - >> - char e_ident[EI_NIDENT]; >> - if (read (fd, e_ident, EI_NIDENT) != EI_NIDENT) >> - goto no_info; >> - >> - close (fd); >> - >> - if (memcmp (e_ident, ELFMAG, SELFMAG) != 0) >> - { >> - error (0, 0, gettext ("process %lu is no ELF program"), pid); >> - return EXIT_FAILURE; >> - } >> - >> - fd = openat (dfd, "auxv", O_RDONLY); >> - if (fd == -1) >> - goto no_info; >> - >> - size_t auxv_size = 0; >> - void *auxv = NULL; >> - while (1) >> - { >> - auxv_size += 512; >> - auxv = xrealloc (auxv, auxv_size); >> - >> - ssize_t n = pread (fd, auxv, auxv_size, 0); >> - if (n < 0) >> - goto no_info; >> - if (n < auxv_size) >> - { >> - auxv_size = n; >> - break; >> - } >> - } >> - >> - close (fd); >> - >> - int retval; >> - if (e_ident[EI_CLASS] == ELFCLASS32) >> - retval = find_maps32 (pid, auxv, auxv_size); >> - else >> - retval = find_maps64 (pid, auxv, auxv_size); >> - >> - free (auxv); >> - close (memfd); >> - >> - return retval; >> -} >> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile >> index 52ac6ad484..e799473b17 100644 >> --- a/sysdeps/unix/sysv/linux/Makefile >> +++ b/sysdeps/unix/sysv/linux/Makefile >> @@ -212,10 +212,6 @@ sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \ >> dl-fxstatat64 >> libof-lddlibc4 = lddlibc4 >> - >> -others += pldd >> -install-bin += pldd >> -$(objpfx)pldd: $(objpfx)xmalloc.o >> endif >> ifeq ($(subdir),rt) >> > >
On 10/04/2019 17:41, Adhemerval Zanella wrote: > > > On 10/04/2019 17:17, Carlos O'Donell wrote: >> On 4/10/19 3:04 PM, Adhemerval Zanella wrote: >> [...] >> This requires python in base runtime, and might not be on a production >> container instance. > > Ok, the question I have is whether we want to make pldd still poke the loader > internal data structures directly using /proc/<pid>/mem plus ptrace or if we > can use a simplified version by parsing /proc/<pid>/maps (as lsof does). Another possible caveat that came to my mind of coding pldd by poking on loader internal data is it ties the pldd to installed glibc (meaning that a new pldd version might fail on older glibc or vice-versa).
On 4/10/19 4:41 PM, Adhemerval Zanella wrote: > > > On 10/04/2019 17:17, Carlos O'Donell wrote: >> On 4/10/19 3:04 PM, Adhemerval Zanella wrote: >>> As reported in bugzilla itself and on man-pages [1] it has been >>> broken since 2.19. Also, its design tie glibc internal definition >>> by duplicate struct layouts and dynamic loader objects. >> >> I have a sustained objection to removing pldd. >> >> Have we looked into why it's broken? > > I will take a second look, but my wildly guess is most likely some > internal linker interface has drift from pldd internal definitions > and when it tries to poke on process memory it reads invalid memory. OK, please share what you find if you think we need to do this some other way. >> >> I'm happy to add a test-in-container runtime test for it so we can >> see breakage when it happens. >> >> It matches the same Solaris tool pldd, and is a useful utility. > > I agree it is useful, but the same functionally can be provided with > different tools and/or kernel facilities. The advantage I see that > pldd might have it is suppose to poke on internal glibc data structures > and get the loaded dynamic shared objects without relying on additional > kernel information. Right. Could we link pldd against ld.so and export an external private interface that allows pldd to use ld.so to read another ld.so's memory? We always assume pldd and ld.so match the system glibc, so this would remove the problem that we're relying on duplicate information. In eu-ldd this was much easier because I only needed to read ELF, and so that's well structured, but I do need to duplicate a lot of ld.so logic, but I found that a win, in that eu-ldd ends up being a clean-room implementation of the loading logic for testing comparison. pldd is not that. >> This requires python in base runtime, and might not be on a production >> container instance. > > Ok, the question I have is whether we want to make pldd still poke the loader > internal data structures directly using /proc/<pid>/mem plus ptrace or if we > can use a simplified version by parsing /proc/<pid>/maps (as lsof does). > > For latter, why we can't use use lsof instead? Do we really need to provide > a similar tool (but will less features)? lsof is a completely different installed package on most rpm derivative distros. While pldd is in glibc-common and always installed. Having a smaller tool that provides a quick way to check libs in the process. I don't think the maintenance cost outweights the benefit (yet). I think we actually need *more* tooling like this, like lari: https://docs.oracle.com/cd/E36784_01/html/E36870/lari-1.html All of which are useful in debugging production issues on production systems that don't always have all the tools you want available, but have a small set of sanity-checking tools. Packaging a minimum set of useful tools with glibc ensures that the project as a whole delivers what is required to inspect and debug processes that use glibc and act as examples for what we believe is required to debug glibc-linked proccesses. In summary I don't think we're at the tipping point of throwing away pldd. -- Cheers, Carlos.
On 4/10/19 4:45 PM, Adhemerval Zanella wrote: > > > On 10/04/2019 17:41, Adhemerval Zanella wrote: >> >> >> On 10/04/2019 17:17, Carlos O'Donell wrote: >>> On 4/10/19 3:04 PM, Adhemerval Zanella wrote: >>> [...] >>> This requires python in base runtime, and might not be on a production >>> container instance. >> >> Ok, the question I have is whether we want to make pldd still poke the loader >> internal data structures directly using /proc/<pid>/mem plus ptrace or if we >> can use a simplified version by parsing /proc/<pid>/maps (as lsof does). > > Another possible caveat that came to my mind of coding pldd by poking on > loader internal data is it ties the pldd to installed glibc (meaning that > a new pldd version might fail on older glibc or vice-versa). That is 100% OK. You must not do this. You must use matching tooling. -- Cheers, Carlos.
On Apr 10 2019, Carlos O'Donell <codonell@redhat.com> wrote:
> While pldd is in glibc-common and always installed.
pldd is in glibc-utils, and not installed by default.
Andreas.
--
Andreas Schwab, SUSE Labs, schwab@suse.de
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE 1748 E4D4 88E3 0EEA B9D7
"And now for something completely different."
On 4/11/19 3:42 AM, Andreas Schwab wrote: > On Apr 10 2019, Carlos O'Donell <codonell@redhat.com> wrote: > >> While pldd is in glibc-common and always installed. > > pldd is in glibc-utils, and not installed by default. It's in glibc-common in Fedora because ldd is there too. Our glibc-utils has only memusage, memusagestat, mtrace, pcporfiledump, and xtrace. There is an argument for it to be in glibc-utils. Either way my objection is that we are removing pldd without doing a proper RCA to determine if the cost to fix is higher than the cost to continue using it. If we can't fix it then that's OK, we can kill it, but the tool could be used to reveal even more interesting things like when run on a program with LD_AUDIT auditors it could show the DSOs in the private namespace with a note that they are in the private namespace. That does indeed require some sharing, but we build it with glibc so we can share any code we need even at the linked object level. -- Cheers, Carlos.
diff --git a/elf/pldd-xx.c b/elf/pldd-xx.c deleted file mode 100644 index 547f840ee1..0000000000 --- a/elf/pldd-xx.c +++ /dev/null @@ -1,251 +0,0 @@ -/* Copyright (C) 2011-2019 Free Software Foundation, Inc. - This file is part of the GNU C Library. - Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - The GNU C Library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; if not, see - <http://www.gnu.org/licenses/>. */ - -#define E(name) E_(name, CLASS) -#define E_(name, cl) E__(name, cl) -#define E__(name, cl) name##cl -#define EW(type) EW_(Elf, CLASS, type) -#define EW_(e, w, t) EW__(e, w, _##t) -#define EW__(e, w, t) e##w##t - -#define pldd_assert(name, exp) \ - typedef int __assert_##name[((exp) != 0) - 1] - - -struct E(link_map) -{ - EW(Addr) l_addr; - EW(Addr) l_name; - EW(Addr) l_ld; - EW(Addr) l_next; - EW(Addr) l_prev; - EW(Addr) l_real; - Lmid_t l_ns; - EW(Addr) l_libname; -}; -#if CLASS == __ELF_NATIVE_CLASS -pldd_assert (l_addr, (offsetof (struct link_map, l_addr) - == offsetof (struct E(link_map), l_addr))); -pldd_assert (l_name, (offsetof (struct link_map, l_name) - == offsetof (struct E(link_map), l_name))); -pldd_assert (l_next, (offsetof (struct link_map, l_next) - == offsetof (struct E(link_map), l_next))); -#endif - - -struct E(libname_list) -{ - EW(Addr) name; - EW(Addr) next; -}; -#if CLASS == __ELF_NATIVE_CLASS -pldd_assert (name, (offsetof (struct libname_list, name) - == offsetof (struct E(libname_list), name))); -pldd_assert (next, (offsetof (struct libname_list, next) - == offsetof (struct E(libname_list), next))); -#endif - -struct E(r_debug) -{ - int r_version; -#if CLASS == 64 - int pad; -#endif - EW(Addr) r_map; -}; -#if CLASS == __ELF_NATIVE_CLASS -pldd_assert (r_version, (offsetof (struct r_debug, r_version) - == offsetof (struct E(r_debug), r_version))); -pldd_assert (r_map, (offsetof (struct r_debug, r_map) - == offsetof (struct E(r_debug), r_map))); -#endif - - -static int - -E(find_maps) (pid_t pid, void *auxv, size_t auxv_size) -{ - EW(Addr) phdr = 0; - unsigned int phnum = 0; - unsigned int phent = 0; - - EW(auxv_t) *auxvXX = (EW(auxv_t) *) auxv; - for (int i = 0; i < auxv_size / sizeof (EW(auxv_t)); ++i) - switch (auxvXX[i].a_type) - { - case AT_PHDR: - phdr = auxvXX[i].a_un.a_val; - break; - case AT_PHNUM: - phnum = auxvXX[i].a_un.a_val; - break; - case AT_PHENT: - phent = auxvXX[i].a_un.a_val; - break; - default: - break; - } - - if (phdr == 0 || phnum == 0 || phent == 0) - error (EXIT_FAILURE, 0, gettext ("cannot find program header of process")); - - EW(Phdr) *p = alloca (phnum * phent); - if (pread64 (memfd, p, phnum * phent, phdr) != phnum * phent) - { - error (0, 0, gettext ("cannot read program header")); - return EXIT_FAILURE; - } - - /* Determine the load offset. We need this for interpreting the - other program header entries so we do this in a separate loop. - Fortunately it is the first time unless someone does something - stupid when linking the application. */ - EW(Addr) offset = 0; - for (unsigned int i = 0; i < phnum; ++i) - if (p[i].p_type == PT_PHDR) - { - offset = phdr - p[i].p_vaddr; - break; - } - - EW(Addr) list = 0; - char *interp = NULL; - for (unsigned int i = 0; i < phnum; ++i) - if (p[i].p_type == PT_DYNAMIC) - { - EW(Dyn) *dyn = xmalloc (p[i].p_filesz); - if (pread64 (memfd, dyn, p[i].p_filesz, offset + p[i].p_vaddr) - != p[i].p_filesz) - { - error (0, 0, gettext ("cannot read dynamic section")); - return EXIT_FAILURE; - } - - /* Search for the DT_DEBUG entry. */ - for (unsigned int j = 0; j < p[i].p_filesz / sizeof (EW(Dyn)); ++j) - if (dyn[j].d_tag == DT_DEBUG && dyn[j].d_un.d_ptr != 0) - { - struct E(r_debug) r; - if (pread64 (memfd, &r, sizeof (r), dyn[j].d_un.d_ptr) - != sizeof (r)) - { - error (0, 0, gettext ("cannot read r_debug")); - return EXIT_FAILURE; - } - - if (r.r_map != 0) - { - list = r.r_map; - break; - } - } - - free (dyn); - break; - } - else if (p[i].p_type == PT_INTERP) - { - interp = alloca (p[i].p_filesz); - if (pread64 (memfd, interp, p[i].p_filesz, offset + p[i].p_vaddr) - != p[i].p_filesz) - { - error (0, 0, gettext ("cannot read program interpreter")); - return EXIT_FAILURE; - } - } - - if (list == 0) - { - if (interp == NULL) - { - // XXX check whether the executable itself is the loader - return EXIT_FAILURE; - } - - // XXX perhaps try finding ld.so and _r_debug in it - - return EXIT_FAILURE; - } - - /* Print the PID and program name first. */ - printf ("%lu:\t%s\n", (unsigned long int) pid, exe); - - /* Iterate over the list of objects and print the information. */ - struct scratch_buffer tmpbuf; - scratch_buffer_init (&tmpbuf); - int status = 0; - do - { - struct E(link_map) m; - if (pread64 (memfd, &m, sizeof (m), list) != sizeof (m)) - { - error (0, 0, gettext ("cannot read link map")); - status = EXIT_FAILURE; - goto out; - } - - EW(Addr) name_offset = m.l_name; - again: - while (1) - { - ssize_t n = pread64 (memfd, tmpbuf.data, tmpbuf.length, name_offset); - if (n == -1) - { - error (0, 0, gettext ("cannot read object name")); - status = EXIT_FAILURE; - goto out; - } - - if (memchr (tmpbuf.data, '\0', n) != NULL) - break; - - if (!scratch_buffer_grow (&tmpbuf)) - { - error (0, 0, gettext ("cannot allocate buffer for object name")); - status = EXIT_FAILURE; - goto out; - } - } - - if (((char *)tmpbuf.data)[0] == '\0' && name_offset == m.l_name - && m.l_libname != 0) - { - /* Try the l_libname element. */ - struct E(libname_list) ln; - if (pread64 (memfd, &ln, sizeof (ln), m.l_libname) == sizeof (ln)) - { - name_offset = ln.name; - goto again; - } - } - - /* Skip over the executable. */ - if (((char *)tmpbuf.data)[0] != '\0') - printf ("%s\n", (char *)tmpbuf.data); - - list = m.l_next; - } - while (list != 0); - - out: - scratch_buffer_free (&tmpbuf); - return status; -} - - -#undef CLASS diff --git a/elf/pldd.c b/elf/pldd.c deleted file mode 100644 index f3fac4e487..0000000000 --- a/elf/pldd.c +++ /dev/null @@ -1,344 +0,0 @@ -/* List dynamic shared objects linked into given process. - Copyright (C) 2011-2019 Free Software Foundation, Inc. - This file is part of the GNU C Library. - Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - The GNU C Library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; if not, see - <http://www.gnu.org/licenses/>. */ - -#include <alloca.h> -#include <argp.h> -#include <assert.h> -#include <dirent.h> -#include <elf.h> -#include <errno.h> -#include <error.h> -#include <fcntl.h> -#include <libintl.h> -#include <link.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/ptrace.h> -#include <sys/stat.h> -#include <sys/wait.h> -#include <scratch_buffer.h> - -#include <ldsodefs.h> -#include <version.h> - -/* Global variables. */ -extern char *program_invocation_short_name; -#define PACKAGE _libc_intl_domainname - -/* External functions. */ -#include <programs/xmalloc.h> - -/* Name and version of program. */ -static void print_version (FILE *stream, struct argp_state *state); -void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; - -/* Function to print some extra text in the help message. */ -static char *more_help (int key, const char *text, void *input); - -/* Definitions of arguments for argp functions. */ -static const struct argp_option options[] = -{ - { NULL, 0, NULL, 0, NULL } -}; - -/* Short description of program. */ -static const char doc[] = N_("\ -List dynamic shared objects loaded into process."); - -/* Strings for arguments in help texts. */ -static const char args_doc[] = N_("PID"); - -/* Prototype for option handler. */ -static error_t parse_opt (int key, char *arg, struct argp_state *state); - -/* Data structure to communicate with argp functions. */ -static struct argp argp = -{ - options, parse_opt, args_doc, doc, NULL, more_help, NULL -}; - -// File descriptor of /proc/*/mem file. -static int memfd; - -/* Name of the executable */ -static char *exe; - -/* Local functions. */ -static int get_process_info (int dfd, long int pid); -static void wait_for_ptrace_stop (long int pid); - - -int -main (int argc, char *argv[]) -{ - /* Parse and process arguments. */ - int remaining; - argp_parse (&argp, argc, argv, 0, &remaining, NULL); - - if (remaining != argc - 1) - { - fprintf (stderr, - gettext ("Exactly one parameter with process ID required.\n")); - argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name); - return 1; - } - - assert (sizeof (pid_t) == sizeof (int) - || sizeof (pid_t) == sizeof (long int)); - char *endp; - errno = 0; - long int pid = strtol (argv[remaining], &endp, 10); - if (pid < 0 || (pid == ULONG_MAX && errno == ERANGE) || *endp != '\0' - || (sizeof (pid_t) < sizeof (pid) && pid > INT_MAX)) - error (EXIT_FAILURE, 0, gettext ("invalid process ID '%s'"), - argv[remaining]); - - /* Determine the program name. */ - char buf[7 + 3 * sizeof (pid)]; - snprintf (buf, sizeof (buf), "/proc/%lu", pid); - int dfd = open (buf, O_RDONLY | O_DIRECTORY); - if (dfd == -1) - error (EXIT_FAILURE, errno, gettext ("cannot open %s"), buf); - - struct scratch_buffer exebuf; - scratch_buffer_init (&exebuf); - ssize_t nexe; - while ((nexe = readlinkat (dfd, "exe", - exebuf.data, exebuf.length)) == exebuf.length) - { - if (!scratch_buffer_grow (&exebuf)) - { - nexe = -1; - break; - } - } - if (nexe == -1) - exe = (char *) "<program name undetermined>"; - else - { - exe = exebuf.data; - exe[nexe] = '\0'; - } - - /* Stop all threads since otherwise the list of loaded modules might - change while we are reading it. */ - struct thread_list - { - pid_t tid; - struct thread_list *next; - } *thread_list = NULL; - - int taskfd = openat (dfd, "task", O_RDONLY | O_DIRECTORY | O_CLOEXEC); - if (taskfd == 1) - error (EXIT_FAILURE, errno, gettext ("cannot open %s/task"), buf); - DIR *dir = fdopendir (taskfd); - if (dir == NULL) - error (EXIT_FAILURE, errno, gettext ("cannot prepare reading %s/task"), - buf); - - struct dirent64 *d; - while ((d = readdir64 (dir)) != NULL) - { - if (! isdigit (d->d_name[0])) - continue; - - errno = 0; - long int tid = strtol (d->d_name, &endp, 10); - if (tid < 0 || (tid == ULONG_MAX && errno == ERANGE) || *endp != '\0' - || (sizeof (pid_t) < sizeof (pid) && tid > INT_MAX)) - error (EXIT_FAILURE, 0, gettext ("invalid thread ID '%s'"), - d->d_name); - - if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0) - { - /* There might be a race between reading the directory and - threads terminating. Ignore errors attaching to unknown - threads unless this is the main thread. */ - if (errno == ESRCH && tid != pid) - continue; - - error (EXIT_FAILURE, errno, gettext ("cannot attach to process %lu"), - tid); - } - - wait_for_ptrace_stop (tid); - - struct thread_list *newp = alloca (sizeof (*newp)); - newp->tid = tid; - newp->next = thread_list; - thread_list = newp; - } - - closedir (dir); - - int status = get_process_info (dfd, pid); - - assert (thread_list != NULL); - do - { - ptrace (PTRACE_DETACH, thread_list->tid, NULL, NULL); - thread_list = thread_list->next; - } - while (thread_list != NULL); - - close (dfd); - - return status; -} - - -/* Wait for PID to enter ptrace-stop state after being attached. */ -static void -wait_for_ptrace_stop (long int pid) -{ - int status; - - /* While waiting for SIGSTOP being delivered to the tracee we have to - reinject any other pending signal. Ignore all other errors. */ - while (waitpid (pid, &status, __WALL) == pid && WIFSTOPPED (status)) - { - /* The STOP signal should not be delivered to the tracee. */ - if (WSTOPSIG (status) == SIGSTOP) - return; - if (ptrace (PTRACE_CONT, pid, NULL, - (void *) (uintptr_t) WSTOPSIG (status))) - /* The only possible error is that the process died. */ - return; - } -} - - -/* Handle program arguments. */ -static error_t -parse_opt (int key, char *arg, struct argp_state *state) -{ - switch (key) - { - default: - return ARGP_ERR_UNKNOWN; - } - return 0; -} - - -/* Print bug-reporting information in the help message. */ -static char * -more_help (int key, const char *text, void *input) -{ - char *tp = NULL; - switch (key) - { - case ARGP_KEY_HELP_EXTRA: - /* We print some extra information. */ - if (asprintf (&tp, gettext ("\ -For bug reporting instructions, please see:\n\ -%s.\n"), REPORT_BUGS_TO) < 0) - return NULL; - return tp; - default: - break; - } - return (char *) text; -} - -/* Print the version information. */ -static void -print_version (FILE *stream, struct argp_state *state) -{ - fprintf (stream, "pldd %s%s\n", PKGVERSION, VERSION); - fprintf (stream, gettext ("\ -Copyright (C) %s Free Software Foundation, Inc.\n\ -This is free software; see the source for copying conditions. There is NO\n\ -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ -"), "2019"); - fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); -} - - -#define CLASS 32 -#include "pldd-xx.c" -#define CLASS 64 -#include "pldd-xx.c" - - -static int -get_process_info (int dfd, long int pid) -{ - memfd = openat (dfd, "mem", O_RDONLY); - if (memfd == -1) - goto no_info; - - int fd = openat (dfd, "exe", O_RDONLY); - if (fd == -1) - { - no_info: - error (0, errno, gettext ("cannot get information about process %lu"), - pid); - return EXIT_FAILURE; - } - - char e_ident[EI_NIDENT]; - if (read (fd, e_ident, EI_NIDENT) != EI_NIDENT) - goto no_info; - - close (fd); - - if (memcmp (e_ident, ELFMAG, SELFMAG) != 0) - { - error (0, 0, gettext ("process %lu is no ELF program"), pid); - return EXIT_FAILURE; - } - - fd = openat (dfd, "auxv", O_RDONLY); - if (fd == -1) - goto no_info; - - size_t auxv_size = 0; - void *auxv = NULL; - while (1) - { - auxv_size += 512; - auxv = xrealloc (auxv, auxv_size); - - ssize_t n = pread (fd, auxv, auxv_size, 0); - if (n < 0) - goto no_info; - if (n < auxv_size) - { - auxv_size = n; - break; - } - } - - close (fd); - - int retval; - if (e_ident[EI_CLASS] == ELFCLASS32) - retval = find_maps32 (pid, auxv, auxv_size); - else - retval = find_maps64 (pid, auxv, auxv_size); - - free (auxv); - close (memfd); - - return retval; -} diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile index 52ac6ad484..e799473b17 100644 --- a/sysdeps/unix/sysv/linux/Makefile +++ b/sysdeps/unix/sysv/linux/Makefile @@ -212,10 +212,6 @@ sysdep-rtld-routines += dl-brk dl-sbrk dl-getcwd dl-openat64 dl-opendir \ dl-fxstatat64 libof-lddlibc4 = lddlibc4 - -others += pldd -install-bin += pldd -$(objpfx)pldd: $(objpfx)xmalloc.o endif ifeq ($(subdir),rt)