[1/2] ld.so: Implement the --list-diagnostics option

Message ID 875z2hshtn.fsf@oldenburg.str.redhat.com
State Superseded
Headers
Series [1/2] ld.so: Implement the --list-diagnostics option |

Commit Message

Florian Weimer Feb. 24, 2021, 1:42 p.m. UTC
  ---
 NEWS                             |   4 +
 elf/Makefile                     |   5 +-
 elf/dl-diagnostics.c             | 327 +++++++++++++++++++++++++++++++++++++++
 elf/dl-main.h                    |   5 +-
 elf/dl-usage.c                   |   1 +
 elf/rtld.c                       |  18 ++-
 sysdeps/generic/dl-diagnostics.h |  28 ++++
 sysdeps/generic/ldsodefs.h       |  14 ++
 8 files changed, 394 insertions(+), 8 deletions(-)
  

Comments

Florian Weimer March 1, 2021, 9:12 a.m. UTC | #1
I'd appreciate a review of this.

  <https://sourceware.org/pipermail/libc-alpha/2021-February/122973.html>
  <https://sourceware.org/pipermail/libc-alpha/2021-February/122972.html>

I think this, or something very much like it, will be needed to diagnose
glibc-hwcaps and IFUNC selection issues.

Thanks,
Florian
  
H.J. Lu March 1, 2021, 11:13 p.m. UTC | #2
On Wed, Feb 24, 2021 at 6:59 AM Florian Weimer via Libc-alpha
<libc-alpha@sourceware.org> wrote:
>
> ---
>  NEWS                             |   4 +
>  elf/Makefile                     |   5 +-
>  elf/dl-diagnostics.c             | 327 +++++++++++++++++++++++++++++++++++++++
>  elf/dl-main.h                    |   5 +-
>  elf/dl-usage.c                   |   1 +
>  elf/rtld.c                       |  18 ++-
>  sysdeps/generic/dl-diagnostics.h |  28 ++++
>  sysdeps/generic/ldsodefs.h       |  14 ++
>  8 files changed, 394 insertions(+), 8 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 37ba6ac0d0..73a1a0df97 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -14,6 +14,10 @@ Major new features:
>    constant on Linux.  MINSIGSTKSZ is redefined to sysconf(_SC_MINSIGSTKSZ)
>    and SIGSTKSZ is redefined to sysconf (_SC_SIGSTKSZ).
>
> +* The dynamic linker implements the --list-diagnostics option, printing
> +  a dump of information related to IFUNC resolver operation and
> +  glibc-hwcaps subdirectory selection.
> +
>  Deprecated and removed features, and other changes affecting compatibility:
>
>    [Add deprecations, removals and changes affecting compatibility here]
> diff --git a/elf/Makefile b/elf/Makefile
> index 16c89b6d07..e272a72ccf 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -66,7 +66,7 @@ elide-routines.os = $(all-dl-routines) dl-support enbl-secure dl-origin \
>  # interpreter and operating independent of libc.
>  rtld-routines  = rtld $(all-dl-routines) dl-sysdep dl-environ dl-minimal \
>    dl-error-minimal dl-conflict dl-hwcaps dl-hwcaps_split dl-hwcaps-subdirs \
> -  dl-usage
> +  dl-usage dl-diagnostics
>  all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
>
>  CFLAGS-dl-runtime.c += -fexceptions -fasynchronous-unwind-tables
> @@ -677,6 +677,9 @@ CFLAGS-cache.c += $(SYSCONF-FLAGS)
>  CFLAGS-rtld.c += $(SYSCONF-FLAGS)
>  CFLAGS-dl-usage.c += $(SYSCONF-FLAGS) \
>    -D'RTLD="$(rtlddir)/$(rtld-installed-name)"'
> +CFLAGS-dl-diagnostics.c += $(SYSCONF-FLAGS) \
> +  -D'PREFIX="$(prefix)"' \
> +  -D'RTLD="$(rtlddir)/$(rtld-installed-name)"'
>
>  cpp-srcs-left := $(all-rtld-routines:=.os)
>  lib := rtld
> diff --git a/elf/dl-diagnostics.c b/elf/dl-diagnostics.c
> new file mode 100644
> index 0000000000..778592b76b
> --- /dev/null
> +++ b/elf/dl-diagnostics.c
> @@ -0,0 +1,327 @@
> +/* Print diagnostics data in ld.so.
> +   Copyright (C) 2021 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   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
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <gnu/lib-names.h>
> +#include <ldsodefs.h>
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <sys/utsname.h>
> +#include <unistd.h>
> +
> +#include <dl-hwcaps.h>
> +#include <dl-procinfo.h>
> +#include <dl-sysdep.h>
> +#include "trusted-dirs.h"
> +#include "version.h"
> +
> +#include <dl-diagnostics.h>
> +
> +/* Write CH to standard output.  */
> +static void
> +_dl_putc (char ch)
> +{
> +  _dl_write (STDOUT_FILENO, &ch, 1);
> +}
> +
> +/* Print CH to standard output, quoting it if necessary. */
> +static void
> +print_quoted_char (char ch)
> +{
> +  if (ch < ' ' || ch > '~')
> +    {
> +      char buf[4];
> +      buf[0] = '\\';
> +      buf[1] = '0' + ((ch >> 6) & 7);
> +      buf[2] = '0' + ((ch >> 6) & 7);
> +      buf[3] = '0' + (ch & 7);
> +      _dl_write (STDOUT_FILENO, buf, 4);
> +    }
> +  else
> +    {
> +      if (ch == '\\' || ch == '"')
> +        _dl_putc ('\\');
> +      _dl_putc (ch);
> +    }
> +}
> +
> +/* Print S of LEN bytes to standard output, quoting characters as
> +   needed.  */
> +static void
> +print_string_length (const char *s, size_t len)
> +{
> +  _dl_putc ('"');
> +  for (size_t i = 0; i < len; ++i)
> +    print_quoted_char (s[i]);
> +  _dl_putc ('"');
> +}
> +
> +void
> +_dl_diagnostics_print_string (const char *s)
> +{
> +  if (s == NULL)
> +    {
> +      _dl_printf ("0x0");
> +      return;
> +    }
> +
> +  _dl_putc ('"');
> +  while (*s != '\0')
> +    {
> +      print_quoted_char (*s);
> +      ++s;
> +    }
> +  _dl_putc ('"');
> +}
> +
> +void
> +_dl_diagnostics_print_labeled_string (const char *label, const char *s)
> +{
> +  _dl_printf ("%s=", label);
> +  _dl_diagnostics_print_string (s);
> +  _dl_putc ('\n');
> +}
> +
> +void
> +_dl_diagnostics_print_labeled_value (const char *label, uint64_t value)
> +{
> +  if (sizeof (value) == sizeof (unsigned long int))
> +    /* _dl_printf can print 64-bit values directly.  */
> +    _dl_printf ("%s=0x%lx\n", label, (unsigned long int) value);
> +  else
> +    {
> +      uint32_t high = value >> 32;
> +      uint32_t low = value;
> +      if (high == 0)
> +        _dl_printf ("%s=0x%x\n", label, low);
> +      else
> +        _dl_printf ("%s=0x%x%08x\n", label, high, low);
> +    }
> +}

You want to print out everything in strings and values.

> +/* Return true if ENV is an unfiltered environment variable.  */
> +static bool
> +unfiltered_envvar (const char *env, size_t *name_length)
> +{
> +  char *env_equal = strchr (env, '=');
> +  if (env_equal == NULL)
> +    {
> +      /* Always dump malformed entries.  */
> +      *name_length = strlen (env);
> +      return true;
> +    }
> +  size_t envname_length = env_equal - env;
> +  *name_length = envname_length;
> +
> +  /* LC_ and LD_ variables.  */
> +  if (env[0] == 'L' && (env[1] == 'C' || env[1] == 'D')
> +      && env[2] == '_')
> +    return true;
> +
> +  /* MALLOC_ variables.  */
> +  if (strncmp (env, "MALLOC_", strlen ("MALLOC_")) == 0)
> +    return true;
> +
> +  static const char unfiltered[] =
> +    "DATEMSK\0"
> +    "GCONV_PATH\0"
> +    "GETCONF_DIR\0"
> +    "GETCONF_DIR\0"
> +    "GLIBC_TUNABLES\0"
> +    "GMON_OUTPUT_PREFIX\0"
> +    "HESIOD_CONFIG\0"
> +    "HES_DOMAIN\0"
> +    "HOSTALIASES\0"
> +    "I18NPATH\0"
> +    "IFS\0"
> +    "LANG\0"
> +    "LOCALDOMAIN\0"
> +    "LOCPATH\0"
> +    "MSGVERB\0"
> +    "NIS_DEFAULTS\0"
> +    "NIS_GROUP\0"
> +    "NIS_PATH\0"
> +    "NLSPATH\0"
> +    "PATH\0"
> +    "POSIXLY_CORRECT\0"
> +    "RESOLV_HOST_CONF\0"
> +    "RES_OPTIONS\0"
> +    "SEV_LEVEL\0"
> +    "TMPDIR\0"
> +    "TZ\0"
> +    "TZDIR\0"

Any particular reason to have double '\0' at the end of string?
Will a single  '\0' work?

> +    ;
> +  for (const char *candidate = unfiltered; *candidate != '\0'; )
> +    {
> +      size_t candidate_length = strlen (candidate);
> +      if (candidate_length == envname_length
> +          && memcmp (candidate, env, candidate_length) == 0)
> +        return true;
> +      candidate += candidate_length + 1;
> +    }
> +
> +  return false;
> +}
> +
> +/* Dump the process environment.  */
> +static void
> +print_environ (char **environ)
> +{
> +  unsigned int index = 0;
> +  for (char **envp = environ; *envp != NULL; ++envp)
> +    {
> +      char *env = *envp;
> +      size_t name_length;
> +      bool unfiltered = unfiltered_envvar (env, &name_length);
> +      _dl_printf ("env%s[0x%x]=",
> +                  unfiltered ? "" : "_filtered", index);
> +      if (unfiltered)
> +        _dl_diagnostics_print_string (env);
> +      else
> +        print_string_length (env, name_length);
> +      _dl_putc ('\n');
> +      ++index;
> +    }
> +}
> +
> +/* Print configured paths and the built-in search path.  */
> +static void
> +print_paths (void)
> +{
> +  _dl_diagnostics_print_labeled_string ("path.prefix", PREFIX);
> +  _dl_diagnostics_print_labeled_string ("path.rtld", RTLD);
> +  _dl_diagnostics_print_labeled_string ("path.sysconfdir", SYSCONFDIR);
> +
> +  unsigned int index = 0;
> +  static const char *system_dirs = SYSTEM_DIRS "\0";
> +  for (const char *e = system_dirs; *e != '\0'; )
> +    {
> +      size_t len = strlen (e);
> +      _dl_printf ("path.system_dirs[0x%x]=", index);
> +      print_string_length (e, len);
> +      _dl_putc ('\n');
> +      ++index;
> +      e += len + 1;
> +    }
> +}
> +
> +/* On Hurd, uname is not available on ld.so.  This corresponds to a
> +   missing domainname member.  */
> +#define PRINT_UNAME (_UTSNAME_DOMAIN_LENGTH > 0)
> +
> +#if PRINT_UNAME
> +/* Print one uname entry.  */
> +static void
> +print_utsname_entry (const char *field, const char *value)
> +{
> +  _dl_printf ("uname.");
> +  _dl_diagnostics_print_labeled_string (field, value);
> +}
> +
> +/* Print information from uname, including the kernel version.  */
> +static void
> +print_uname (void)
> +{
> +  struct utsname uts;
> +  if (__uname (&uts) == 0)
> +    {
> +      print_utsname_entry ("sysname", uts.sysname);
> +      print_utsname_entry ("nodename", uts.nodename);
> +      print_utsname_entry ("release", uts.release);
> +      print_utsname_entry ("version", uts.version);
> +      print_utsname_entry ("machine", uts.machine);
> +      print_utsname_entry ("domainname", uts.domainname);
> +    }
> +}
> +#endif

Put it under sysdeps/generic?

> +/* Print information about the glibc version.  */
> +static void
> +print_version (void)
> +{
> +  _dl_diagnostics_print_labeled_string ("version.release", RELEASE);
> +  _dl_diagnostics_print_labeled_string ("version.version", VERSION);
> +}
> +
> +#ifdef HAVE_AUX_VECTOR
> +/* Dump the auxiliary vector to standard output.  */
> +static void
> +print_auxv (void)
> +{
> +  /* See _dl_show_auxv.  The code below follows the general output
> +     format for diagnostic dumps.  */
> +  unsigned int index = 0;
> +  for (ElfW(auxv_t) *av = GLRO(dl_auxv); av->a_type != AT_NULL; ++av)
> +    {
> +      _dl_printf ("auxv[0x%x].a_type=0x%lx\n"
> +                  "auxv[0x%x].a_val=",
> +                  index, (unsigned long int) av->a_type, index);
> +      if (av->a_type == AT_EXECFN
> +          || av->a_type == AT_PLATFORM
> +          || av->a_type == AT_BASE_PLATFORM)
> +        /* The address of the strings is not useful at all, so print
> +           the strings themselvs.  */
> +        _dl_diagnostics_print_string ((const char *) av->a_un.a_val);
> +      else
> +        _dl_printf ("0x%lx", (unsigned long int) av->a_un.a_val);
> +      _dl_putc ('\n');
> +      ++index;
> +    }
> +}
> +#endif /* HAVE_AUX_VECTOR */
> +
> +void
> +_dl_print_diagnostics (char **environ)
> +{
> +#ifdef HAVE_DL_DISCOVER_OSVERSION
> +  _dl_diagnostics_print_labeled_value
> +    ("dl_discover_osversion", _dl_discover_osversion ());
> +#endif
> +  _dl_diagnostics_print_labeled_string ("dl_dst_lib", DL_DST_LIB);
> +  _dl_diagnostics_print_labeled_value ("dl_hwcap", GLRO (dl_hwcap));
> +  _dl_diagnostics_print_labeled_value ("dl_hwcap_important", HWCAP_IMPORTANT);
> +  _dl_diagnostics_print_labeled_value ("dl_hwcap2", GLRO (dl_hwcap2));
> +  _dl_diagnostics_print_labeled_string
> +    ("dl_hwcaps_subdirs", _dl_hwcaps_subdirs);
> +  _dl_diagnostics_print_labeled_value
> +    ("dl_hwcaps_subdirs_active", _dl_hwcaps_subdirs_active ());
> +  _dl_diagnostics_print_labeled_value ("dl_osversion", GLRO (dl_osversion));
> +  _dl_diagnostics_print_labeled_value ("dl_pagesize", GLRO (dl_pagesize));
> +  _dl_diagnostics_print_labeled_string ("dl_platform", GLRO (dl_platform));
> +  _dl_diagnostics_print_labeled_string
> +    ("dl_profile_output", GLRO (dl_profile_output));
> +  _dl_diagnostics_print_labeled_value
> +    ("dl_string_platform", _dl_string_platform ( GLRO (dl_platform)));
> +
> +  _dl_diagnostics_print_labeled_string ("dso.ld", LD_SO);
> +  _dl_diagnostics_print_labeled_string ("dso.libc", LIBC_SO);
> +
> +  print_environ (environ);
> +  print_paths ();
> +#if PRINT_UNAME
> +  print_uname ();
> +#endif
> +  print_version ();
> +
> +#ifdef HAVE_AUX_VECTOR
> +  print_auxv ();
> +#endif
> +
> +  _dl_diagnostics_sysdeps ();
> +
> +  _exit (EXIT_SUCCESS);
> +}
> diff --git a/elf/dl-main.h b/elf/dl-main.h
> index 3a5e13c739..d3820e0063 100644
> --- a/elf/dl-main.h
> +++ b/elf/dl-main.h
> @@ -63,7 +63,7 @@ struct audit_list
>  enum rtld_mode
>    {
>      rtld_mode_normal, rtld_mode_list, rtld_mode_verify, rtld_mode_trace,
> -    rtld_mode_list_tunables, rtld_mode_help,
> +    rtld_mode_list_tunables, rtld_mode_list_diagnostics, rtld_mode_help,
>    };
>
>  /* Aggregated state information extracted from environment variables
> @@ -121,4 +121,7 @@ _Noreturn void _dl_version (void) attribute_hidden;
>  _Noreturn void _dl_help (const char *argv0, struct dl_main_state *state)
>    attribute_hidden;
>
> +/* Print a diagnostics dump.  */
> +_Noreturn void _dl_print_diagnostics (char **environ) attribute_hidden;
> +
>  #endif /* _DL_MAIN */
> diff --git a/elf/dl-usage.c b/elf/dl-usage.c
> index 6e26818bd7..5ad3a72559 100644
> --- a/elf/dl-usage.c
> +++ b/elf/dl-usage.c
> @@ -261,6 +261,7 @@ setting environment variables (which would be inherited by subprocesses).\n\
>    --list-tunables       list all tunables with minimum and maximum values\n"
>  #endif
>  "\
> +  --list-diagnostics    list diagnostics information\n\
>    --help                display this help and exit\n\
>    --version             output version information and exit\n\
>  \n\
> diff --git a/elf/rtld.c b/elf/rtld.c
> index 596b6ac3d9..94a00e2049 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -141,6 +141,7 @@ static void dl_main_state_init (struct dl_main_state *state);
>  /* Process all environments variables the dynamic linker must recognize.
>     Since all of them start with `LD_' we are a bit smarter while finding
>     all the entries.  */
> +extern char **_environ attribute_hidden;
>  static void process_envvars (struct dl_main_state *state);
>
>  #ifdef DL_ARGV_NOT_RELRO
> @@ -1287,6 +1288,14 @@ dl_main (const ElfW(Phdr) *phdr,
>             ++_dl_argv;
>           }
>  #endif
> +       else if (! strcmp (_dl_argv[1], "--list-diagnostics"))
> +         {
> +           state.mode = rtld_mode_list_diagnostics;
> +
> +           ++_dl_skip_args;
> +           --_dl_argc;
> +           ++_dl_argv;
> +         }
>         else if (strcmp (_dl_argv[1], "--help") == 0)
>           {
>             state.mode = rtld_mode_help;
> @@ -1315,6 +1324,9 @@ dl_main (const ElfW(Phdr) *phdr,
>         }
>  #endif
>
> +      if (state.mode == rtld_mode_list_diagnostics)
> +       _dl_print_diagnostics (_environ);
> +
>        /* If we have no further argument the program was called incorrectly.
>          Grant the user some education.  */
>        if (_dl_argc < 2)
> @@ -2649,12 +2661,6 @@ a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
>      }
>  }
>
> -/* Process all environments variables the dynamic linker must recognize.
> -   Since all of them start with `LD_' we are a bit smarter while finding
> -   all the entries.  */
> -extern char **_environ attribute_hidden;
> -
> -
>  static void
>  process_envvars (struct dl_main_state *state)
>  {
> diff --git a/sysdeps/generic/dl-diagnostics.h b/sysdeps/generic/dl-diagnostics.h
> new file mode 100644
> index 0000000000..5db7416dfe
> --- /dev/null
> +++ b/sysdeps/generic/dl-diagnostics.h
> @@ -0,0 +1,28 @@
> +/* Print diagnostics data in ld.so.
> +   Copyright (C) 2021 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   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
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#ifndef _DL_DIAGNOSTICS_H
> +#define _DL_DIAGNOSTICS_H
> +
> +/* The generic version has no extra information to print.  */
> +static inline void
> +_dl_diagnostics_sysdeps (void)
> +{
> +}
> +
> +#endif /* _DL_DIAGNOSTICS_H */
> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> index 9720a4e446..da0143cf44 100644
> --- a/sysdeps/generic/ldsodefs.h
> +++ b/sysdeps/generic/ldsodefs.h
> @@ -790,6 +790,20 @@ void _dl_fatal_printf (const char *fmt, ...)
>    __attribute__ ((__format__ (__printf__, 1, 2), __noreturn__));
>  rtld_hidden_proto (_dl_fatal_printf)
>
> +/* Write the null-terminated string to standard output, surrounded in
> +   quotation marks.  */
> +void _dl_diagnostics_print_string (const char *s) attribute_hidden;
> +
> +/* Like _dl_diagnostics_print_string, but add a LABEL= prefix, and a
> +   newline character as a suffix.  */
> +void _dl_diagnostics_print_labeled_string (const char *label, const char *s)
> +  attribute_hidden;
> +
> +/* Print LABEL=VALUE to standard output, followed by a newline
> +   character.  */
> +void _dl_diagnostics_print_labeled_value (const char *label, uint64_t value)
> +  attribute_hidden;
> +
>  /* An exception raised by the _dl_signal_error function family and
>     caught by _dl_catch_error function family.  Exceptions themselves
>     are copied as part of the raise operation, but the strings are
>
  
Florian Weimer March 2, 2021, 6 a.m. UTC | #3
* H. J. Lu via Libc-alpha:

>> +void
>> +_dl_diagnostics_print_labeled_value (const char *label, uint64_t value)
>> +{
>> +  if (sizeof (value) == sizeof (unsigned long int))
>> +    /* _dl_printf can print 64-bit values directly.  */
>> +    _dl_printf ("%s=0x%lx\n", label, (unsigned long int) value);
>> +  else
>> +    {
>> +      uint32_t high = value >> 32;
>> +      uint32_t low = value;
>> +      if (high == 0)
>> +        _dl_printf ("%s=0x%x\n", label, low);
>> +      else
>> +        _dl_printf ("%s=0x%x%08x\n", label, high, low);
>> +    }
>> +}
>
> You want to print out everything in strings and values.

If I don't know that the value is a valid pointer, I can't print it as a
string.

>> +  static const char unfiltered[] =
>> +    "DATEMSK\0"
>> +    "GCONV_PATH\0"
>> +    "GETCONF_DIR\0"
>> +    "GETCONF_DIR\0"
>> +    "GLIBC_TUNABLES\0"
>> +    "GMON_OUTPUT_PREFIX\0"
>> +    "HESIOD_CONFIG\0"
>> +    "HES_DOMAIN\0"
>> +    "HOSTALIASES\0"
>> +    "I18NPATH\0"
>> +    "IFS\0"
>> +    "LANG\0"
>> +    "LOCALDOMAIN\0"
>> +    "LOCPATH\0"
>> +    "MSGVERB\0"
>> +    "NIS_DEFAULTS\0"
>> +    "NIS_GROUP\0"
>> +    "NIS_PATH\0"
>> +    "NLSPATH\0"
>> +    "PATH\0"
>> +    "POSIXLY_CORRECT\0"
>> +    "RESOLV_HOST_CONF\0"
>> +    "RES_OPTIONS\0"
>> +    "SEV_LEVEL\0"
>> +    "TMPDIR\0"
>> +    "TZ\0"
>> +    "TZDIR\0"
>
> Any particular reason to have double '\0' at the end of string?
> Will a single  '\0' work?

It's necessary to recognize the end of the list.

>> +/* On Hurd, uname is not available on ld.so.  This corresponds to a
>> +   missing domainname member.  */
>> +#define PRINT_UNAME (_UTSNAME_DOMAIN_LENGTH > 0)
>> +
>> +#if PRINT_UNAME
>> +/* Print one uname entry.  */
>> +static void
>> +print_utsname_entry (const char *field, const char *value)
>> +{
>> +  _dl_printf ("uname.");
>> +  _dl_diagnostics_print_labeled_string (field, value);
>> +}
>> +
>> +/* Print information from uname, including the kernel version.  */
>> +static void
>> +print_uname (void)
>> +{
>> +  struct utsname uts;
>> +  if (__uname (&uts) == 0)
>> +    {
>> +      print_utsname_entry ("sysname", uts.sysname);
>> +      print_utsname_entry ("nodename", uts.nodename);
>> +      print_utsname_entry ("release", uts.release);
>> +      print_utsname_entry ("version", uts.version);
>> +      print_utsname_entry ("machine", uts.machine);
>> +      print_utsname_entry ("domainname", uts.domainname);
>> +    }
>> +}
>> +#endif
>
> Put it under sysdeps/generic?

Okay, will do that.

Thanks,
Florian
  

Patch

diff --git a/NEWS b/NEWS
index 37ba6ac0d0..73a1a0df97 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,10 @@  Major new features:
   constant on Linux.  MINSIGSTKSZ is redefined to sysconf(_SC_MINSIGSTKSZ)
   and SIGSTKSZ is redefined to sysconf (_SC_SIGSTKSZ).
 
+* The dynamic linker implements the --list-diagnostics option, printing
+  a dump of information related to IFUNC resolver operation and
+  glibc-hwcaps subdirectory selection.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
   [Add deprecations, removals and changes affecting compatibility here]
diff --git a/elf/Makefile b/elf/Makefile
index 16c89b6d07..e272a72ccf 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -66,7 +66,7 @@  elide-routines.os = $(all-dl-routines) dl-support enbl-secure dl-origin \
 # interpreter and operating independent of libc.
 rtld-routines	= rtld $(all-dl-routines) dl-sysdep dl-environ dl-minimal \
   dl-error-minimal dl-conflict dl-hwcaps dl-hwcaps_split dl-hwcaps-subdirs \
-  dl-usage
+  dl-usage dl-diagnostics
 all-rtld-routines = $(rtld-routines) $(sysdep-rtld-routines)
 
 CFLAGS-dl-runtime.c += -fexceptions -fasynchronous-unwind-tables
@@ -677,6 +677,9 @@  CFLAGS-cache.c += $(SYSCONF-FLAGS)
 CFLAGS-rtld.c += $(SYSCONF-FLAGS)
 CFLAGS-dl-usage.c += $(SYSCONF-FLAGS) \
   -D'RTLD="$(rtlddir)/$(rtld-installed-name)"'
+CFLAGS-dl-diagnostics.c += $(SYSCONF-FLAGS) \
+  -D'PREFIX="$(prefix)"' \
+  -D'RTLD="$(rtlddir)/$(rtld-installed-name)"'
 
 cpp-srcs-left := $(all-rtld-routines:=.os)
 lib := rtld
diff --git a/elf/dl-diagnostics.c b/elf/dl-diagnostics.c
new file mode 100644
index 0000000000..778592b76b
--- /dev/null
+++ b/elf/dl-diagnostics.c
@@ -0,0 +1,327 @@ 
+/* Print diagnostics data in ld.so.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <gnu/lib-names.h>
+#include <ldsodefs.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include <dl-hwcaps.h>
+#include <dl-procinfo.h>
+#include <dl-sysdep.h>
+#include "trusted-dirs.h"
+#include "version.h"
+
+#include <dl-diagnostics.h>
+
+/* Write CH to standard output.  */
+static void
+_dl_putc (char ch)
+{
+  _dl_write (STDOUT_FILENO, &ch, 1);
+}
+
+/* Print CH to standard output, quoting it if necessary. */
+static void
+print_quoted_char (char ch)
+{
+  if (ch < ' ' || ch > '~')
+    {
+      char buf[4];
+      buf[0] = '\\';
+      buf[1] = '0' + ((ch >> 6) & 7);
+      buf[2] = '0' + ((ch >> 6) & 7);
+      buf[3] = '0' + (ch & 7);
+      _dl_write (STDOUT_FILENO, buf, 4);
+    }
+  else
+    {
+      if (ch == '\\' || ch == '"')
+        _dl_putc ('\\');
+      _dl_putc (ch);
+    }
+}
+
+/* Print S of LEN bytes to standard output, quoting characters as
+   needed.  */
+static void
+print_string_length (const char *s, size_t len)
+{
+  _dl_putc ('"');
+  for (size_t i = 0; i < len; ++i)
+    print_quoted_char (s[i]);
+  _dl_putc ('"');
+}
+
+void
+_dl_diagnostics_print_string (const char *s)
+{
+  if (s == NULL)
+    {
+      _dl_printf ("0x0");
+      return;
+    }
+
+  _dl_putc ('"');
+  while (*s != '\0')
+    {
+      print_quoted_char (*s);
+      ++s;
+    }
+  _dl_putc ('"');
+}
+
+void
+_dl_diagnostics_print_labeled_string (const char *label, const char *s)
+{
+  _dl_printf ("%s=", label);
+  _dl_diagnostics_print_string (s);
+  _dl_putc ('\n');
+}
+
+void
+_dl_diagnostics_print_labeled_value (const char *label, uint64_t value)
+{
+  if (sizeof (value) == sizeof (unsigned long int))
+    /* _dl_printf can print 64-bit values directly.  */
+    _dl_printf ("%s=0x%lx\n", label, (unsigned long int) value);
+  else
+    {
+      uint32_t high = value >> 32;
+      uint32_t low = value;
+      if (high == 0)
+        _dl_printf ("%s=0x%x\n", label, low);
+      else
+        _dl_printf ("%s=0x%x%08x\n", label, high, low);
+    }
+}
+
+/* Return true if ENV is an unfiltered environment variable.  */
+static bool
+unfiltered_envvar (const char *env, size_t *name_length)
+{
+  char *env_equal = strchr (env, '=');
+  if (env_equal == NULL)
+    {
+      /* Always dump malformed entries.  */
+      *name_length = strlen (env);
+      return true;
+    }
+  size_t envname_length = env_equal - env;
+  *name_length = envname_length;
+
+  /* LC_ and LD_ variables.  */
+  if (env[0] == 'L' && (env[1] == 'C' || env[1] == 'D')
+      && env[2] == '_')
+    return true;
+
+  /* MALLOC_ variables.  */
+  if (strncmp (env, "MALLOC_", strlen ("MALLOC_")) == 0)
+    return true;
+
+  static const char unfiltered[] =
+    "DATEMSK\0"
+    "GCONV_PATH\0"
+    "GETCONF_DIR\0"
+    "GETCONF_DIR\0"
+    "GLIBC_TUNABLES\0"
+    "GMON_OUTPUT_PREFIX\0"
+    "HESIOD_CONFIG\0"
+    "HES_DOMAIN\0"
+    "HOSTALIASES\0"
+    "I18NPATH\0"
+    "IFS\0"
+    "LANG\0"
+    "LOCALDOMAIN\0"
+    "LOCPATH\0"
+    "MSGVERB\0"
+    "NIS_DEFAULTS\0"
+    "NIS_GROUP\0"
+    "NIS_PATH\0"
+    "NLSPATH\0"
+    "PATH\0"
+    "POSIXLY_CORRECT\0"
+    "RESOLV_HOST_CONF\0"
+    "RES_OPTIONS\0"
+    "SEV_LEVEL\0"
+    "TMPDIR\0"
+    "TZ\0"
+    "TZDIR\0"
+    ;
+  for (const char *candidate = unfiltered; *candidate != '\0'; )
+    {
+      size_t candidate_length = strlen (candidate);
+      if (candidate_length == envname_length
+          && memcmp (candidate, env, candidate_length) == 0)
+        return true;
+      candidate += candidate_length + 1;
+    }
+
+  return false;
+}
+
+/* Dump the process environment.  */
+static void
+print_environ (char **environ)
+{
+  unsigned int index = 0;
+  for (char **envp = environ; *envp != NULL; ++envp)
+    {
+      char *env = *envp;
+      size_t name_length;
+      bool unfiltered = unfiltered_envvar (env, &name_length);
+      _dl_printf ("env%s[0x%x]=",
+                  unfiltered ? "" : "_filtered", index);
+      if (unfiltered)
+        _dl_diagnostics_print_string (env);
+      else
+        print_string_length (env, name_length);
+      _dl_putc ('\n');
+      ++index;
+    }
+}
+
+/* Print configured paths and the built-in search path.  */
+static void
+print_paths (void)
+{
+  _dl_diagnostics_print_labeled_string ("path.prefix", PREFIX);
+  _dl_diagnostics_print_labeled_string ("path.rtld", RTLD);
+  _dl_diagnostics_print_labeled_string ("path.sysconfdir", SYSCONFDIR);
+
+  unsigned int index = 0;
+  static const char *system_dirs = SYSTEM_DIRS "\0";
+  for (const char *e = system_dirs; *e != '\0'; )
+    {
+      size_t len = strlen (e);
+      _dl_printf ("path.system_dirs[0x%x]=", index);
+      print_string_length (e, len);
+      _dl_putc ('\n');
+      ++index;
+      e += len + 1;
+    }
+}
+
+/* On Hurd, uname is not available on ld.so.  This corresponds to a
+   missing domainname member.  */
+#define PRINT_UNAME (_UTSNAME_DOMAIN_LENGTH > 0)
+
+#if PRINT_UNAME
+/* Print one uname entry.  */
+static void
+print_utsname_entry (const char *field, const char *value)
+{
+  _dl_printf ("uname.");
+  _dl_diagnostics_print_labeled_string (field, value);
+}
+
+/* Print information from uname, including the kernel version.  */
+static void
+print_uname (void)
+{
+  struct utsname uts;
+  if (__uname (&uts) == 0)
+    {
+      print_utsname_entry ("sysname", uts.sysname);
+      print_utsname_entry ("nodename", uts.nodename);
+      print_utsname_entry ("release", uts.release);
+      print_utsname_entry ("version", uts.version);
+      print_utsname_entry ("machine", uts.machine);
+      print_utsname_entry ("domainname", uts.domainname);
+    }
+}
+#endif
+
+/* Print information about the glibc version.  */
+static void
+print_version (void)
+{
+  _dl_diagnostics_print_labeled_string ("version.release", RELEASE);
+  _dl_diagnostics_print_labeled_string ("version.version", VERSION);
+}
+
+#ifdef HAVE_AUX_VECTOR
+/* Dump the auxiliary vector to standard output.  */
+static void
+print_auxv (void)
+{
+  /* See _dl_show_auxv.  The code below follows the general output
+     format for diagnostic dumps.  */
+  unsigned int index = 0;
+  for (ElfW(auxv_t) *av = GLRO(dl_auxv); av->a_type != AT_NULL; ++av)
+    {
+      _dl_printf ("auxv[0x%x].a_type=0x%lx\n"
+                  "auxv[0x%x].a_val=",
+                  index, (unsigned long int) av->a_type, index);
+      if (av->a_type == AT_EXECFN
+          || av->a_type == AT_PLATFORM
+          || av->a_type == AT_BASE_PLATFORM)
+        /* The address of the strings is not useful at all, so print
+           the strings themselvs.  */
+        _dl_diagnostics_print_string ((const char *) av->a_un.a_val);
+      else
+        _dl_printf ("0x%lx", (unsigned long int) av->a_un.a_val);
+      _dl_putc ('\n');
+      ++index;
+    }
+}
+#endif /* HAVE_AUX_VECTOR */
+
+void
+_dl_print_diagnostics (char **environ)
+{
+#ifdef HAVE_DL_DISCOVER_OSVERSION
+  _dl_diagnostics_print_labeled_value
+    ("dl_discover_osversion", _dl_discover_osversion ());
+#endif
+  _dl_diagnostics_print_labeled_string ("dl_dst_lib", DL_DST_LIB);
+  _dl_diagnostics_print_labeled_value ("dl_hwcap", GLRO (dl_hwcap));
+  _dl_diagnostics_print_labeled_value ("dl_hwcap_important", HWCAP_IMPORTANT);
+  _dl_diagnostics_print_labeled_value ("dl_hwcap2", GLRO (dl_hwcap2));
+  _dl_diagnostics_print_labeled_string
+    ("dl_hwcaps_subdirs", _dl_hwcaps_subdirs);
+  _dl_diagnostics_print_labeled_value
+    ("dl_hwcaps_subdirs_active", _dl_hwcaps_subdirs_active ());
+  _dl_diagnostics_print_labeled_value ("dl_osversion", GLRO (dl_osversion));
+  _dl_diagnostics_print_labeled_value ("dl_pagesize", GLRO (dl_pagesize));
+  _dl_diagnostics_print_labeled_string ("dl_platform", GLRO (dl_platform));
+  _dl_diagnostics_print_labeled_string
+    ("dl_profile_output", GLRO (dl_profile_output));
+  _dl_diagnostics_print_labeled_value
+    ("dl_string_platform", _dl_string_platform ( GLRO (dl_platform)));
+
+  _dl_diagnostics_print_labeled_string ("dso.ld", LD_SO);
+  _dl_diagnostics_print_labeled_string ("dso.libc", LIBC_SO);
+
+  print_environ (environ);
+  print_paths ();
+#if PRINT_UNAME
+  print_uname ();
+#endif
+  print_version ();
+
+#ifdef HAVE_AUX_VECTOR
+  print_auxv ();
+#endif
+
+  _dl_diagnostics_sysdeps ();
+
+  _exit (EXIT_SUCCESS);
+}
diff --git a/elf/dl-main.h b/elf/dl-main.h
index 3a5e13c739..d3820e0063 100644
--- a/elf/dl-main.h
+++ b/elf/dl-main.h
@@ -63,7 +63,7 @@  struct audit_list
 enum rtld_mode
   {
     rtld_mode_normal, rtld_mode_list, rtld_mode_verify, rtld_mode_trace,
-    rtld_mode_list_tunables, rtld_mode_help,
+    rtld_mode_list_tunables, rtld_mode_list_diagnostics, rtld_mode_help,
   };
 
 /* Aggregated state information extracted from environment variables
@@ -121,4 +121,7 @@  _Noreturn void _dl_version (void) attribute_hidden;
 _Noreturn void _dl_help (const char *argv0, struct dl_main_state *state)
   attribute_hidden;
 
+/* Print a diagnostics dump.  */
+_Noreturn void _dl_print_diagnostics (char **environ) attribute_hidden;
+
 #endif /* _DL_MAIN */
diff --git a/elf/dl-usage.c b/elf/dl-usage.c
index 6e26818bd7..5ad3a72559 100644
--- a/elf/dl-usage.c
+++ b/elf/dl-usage.c
@@ -261,6 +261,7 @@  setting environment variables (which would be inherited by subprocesses).\n\
   --list-tunables       list all tunables with minimum and maximum values\n"
 #endif
 "\
+  --list-diagnostics    list diagnostics information\n\
   --help                display this help and exit\n\
   --version             output version information and exit\n\
 \n\
diff --git a/elf/rtld.c b/elf/rtld.c
index 596b6ac3d9..94a00e2049 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -141,6 +141,7 @@  static void dl_main_state_init (struct dl_main_state *state);
 /* Process all environments variables the dynamic linker must recognize.
    Since all of them start with `LD_' we are a bit smarter while finding
    all the entries.  */
+extern char **_environ attribute_hidden;
 static void process_envvars (struct dl_main_state *state);
 
 #ifdef DL_ARGV_NOT_RELRO
@@ -1287,6 +1288,14 @@  dl_main (const ElfW(Phdr) *phdr,
 	    ++_dl_argv;
 	  }
 #endif
+	else if (! strcmp (_dl_argv[1], "--list-diagnostics"))
+	  {
+	    state.mode = rtld_mode_list_diagnostics;
+
+	    ++_dl_skip_args;
+	    --_dl_argc;
+	    ++_dl_argv;
+	  }
 	else if (strcmp (_dl_argv[1], "--help") == 0)
 	  {
 	    state.mode = rtld_mode_help;
@@ -1315,6 +1324,9 @@  dl_main (const ElfW(Phdr) *phdr,
 	}
 #endif
 
+      if (state.mode == rtld_mode_list_diagnostics)
+	_dl_print_diagnostics (_environ);
+
       /* If we have no further argument the program was called incorrectly.
 	 Grant the user some education.  */
       if (_dl_argc < 2)
@@ -2649,12 +2661,6 @@  a filename can be specified using the LD_DEBUG_OUTPUT environment variable.\n");
     }
 }
 
-/* Process all environments variables the dynamic linker must recognize.
-   Since all of them start with `LD_' we are a bit smarter while finding
-   all the entries.  */
-extern char **_environ attribute_hidden;
-
-
 static void
 process_envvars (struct dl_main_state *state)
 {
diff --git a/sysdeps/generic/dl-diagnostics.h b/sysdeps/generic/dl-diagnostics.h
new file mode 100644
index 0000000000..5db7416dfe
--- /dev/null
+++ b/sysdeps/generic/dl-diagnostics.h
@@ -0,0 +1,28 @@ 
+/* Print diagnostics data in ld.so.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _DL_DIAGNOSTICS_H
+#define _DL_DIAGNOSTICS_H
+
+/* The generic version has no extra information to print.  */
+static inline void
+_dl_diagnostics_sysdeps (void)
+{
+}
+
+#endif /* _DL_DIAGNOSTICS_H */
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 9720a4e446..da0143cf44 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -790,6 +790,20 @@  void _dl_fatal_printf (const char *fmt, ...)
   __attribute__ ((__format__ (__printf__, 1, 2), __noreturn__));
 rtld_hidden_proto (_dl_fatal_printf)
 
+/* Write the null-terminated string to standard output, surrounded in
+   quotation marks.  */
+void _dl_diagnostics_print_string (const char *s) attribute_hidden;
+
+/* Like _dl_diagnostics_print_string, but add a LABEL= prefix, and a
+   newline character as a suffix.  */
+void _dl_diagnostics_print_labeled_string (const char *label, const char *s)
+  attribute_hidden;
+
+/* Print LABEL=VALUE to standard output, followed by a newline
+   character.  */
+void _dl_diagnostics_print_labeled_value (const char *label, uint64_t value)
+  attribute_hidden;
+
 /* An exception raised by the _dl_signal_error function family and
    caught by _dl_catch_error function family.  Exceptions themselves
    are copied as part of the raise operation, but the strings are