Squashed 'third_party/elfutils/' content from commit 555e15e

Change-Id: I61cde98949e47e5c8c09c33260de17f30921be79
git-subtree-dir: third_party/elfutils
git-subtree-split: 555e15ebe8bf1eb33d00747173cfc80cc65648a4
diff --git a/libdwfl/linux-kernel-modules.c b/libdwfl/linux-kernel-modules.c
new file mode 100644
index 0000000..9d0fef2
--- /dev/null
+++ b/libdwfl/linux-kernel-modules.c
@@ -0,0 +1,979 @@
+/* Standard libdwfl callbacks for debugging the running Linux kernel.
+   Copyright (C) 2005-2011, 2013, 2014, 2015 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils 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
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+/* In case we have a bad fts we include this before config.h because it
+   can't handle _FILE_OFFSET_BITS.
+   Everything we need here is fine if its declarations just come first.
+   Also, include sys/types.h before fts. On some systems fts.h is not self
+   contained. */
+#ifdef BAD_FTS
+  #include <sys/types.h>
+  #include <fts.h>
+#endif
+
+#include <config.h>
+#include <system.h>
+
+#include "libdwflP.h"
+#include <inttypes.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/utsname.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/* If fts.h is included before config.h, its indirect inclusions may not
+   give us the right LFS aliases of these functions, so map them manually.  */
+#ifdef BAD_FTS
+  #ifdef _FILE_OFFSET_BITS
+    #define open open64
+    #define fopen fopen64
+  #endif
+#else
+  #include <sys/types.h>
+  #include <fts.h>
+#endif
+
+
+#define KERNEL_MODNAME	"kernel"
+
+#define MODULEDIRFMT	"/lib/modules/%s"
+
+#define KNOTESFILE	"/sys/kernel/notes"
+#define	MODNOTESFMT	"/sys/module/%s/notes"
+#define KSYMSFILE	"/proc/kallsyms"
+#define MODULELIST	"/proc/modules"
+#define	SECADDRDIRFMT	"/sys/module/%s/sections/"
+#define MODULE_SECT_NAME_LEN 32	/* Minimum any linux/module.h has had.  */
+
+
+static const char *vmlinux_suffixes[] =
+  {
+    ".gz",
+#ifdef USE_BZLIB
+    ".bz2",
+#endif
+#ifdef USE_LZMA
+    ".xz",
+#endif
+  };
+
+/* Try to open the given file as it is or under the debuginfo directory.  */
+static int
+try_kernel_name (Dwfl *dwfl, char **fname, bool try_debug)
+{
+  if (*fname == NULL)
+    return -1;
+
+  /* Don't bother trying *FNAME itself here if the path will cause it to be
+     tried because we give its own basename as DEBUGLINK_FILE.  */
+  int fd = ((((dwfl->callbacks->debuginfo_path
+	       ? *dwfl->callbacks->debuginfo_path : NULL)
+	      ?: DEFAULT_DEBUGINFO_PATH)[0] == ':') ? -1
+	    : TEMP_FAILURE_RETRY (open (*fname, O_RDONLY)));
+
+  if (fd < 0)
+    {
+      Dwfl_Module fakemod = { .dwfl = dwfl };
+
+      if (try_debug)
+	/* Passing NULL for DEBUGLINK_FILE searches for both the basenamer
+	   "vmlinux" and the default of basename + ".debug", to look for
+	   "vmlinux.debug" files.  */
+	fd = INTUSE(dwfl_standard_find_debuginfo) (&fakemod, NULL, NULL, 0,
+						   *fname, NULL, 0,
+						   &fakemod.debug.name);
+      else
+	/* Try the file's unadorned basename as DEBUGLINK_FILE,
+	   to look only for "vmlinux" files.  */
+	fd = INTUSE(dwfl_standard_find_debuginfo) (&fakemod, NULL, NULL, 0,
+						   *fname, basename (*fname),
+						   0, &fakemod.debug.name);
+
+      if (fakemod.debug.name != NULL)
+	{
+	  free (*fname);
+	  *fname = fakemod.debug.name;
+	}
+    }
+
+  if (fd < 0)
+    for (size_t i = 0;
+	 i < sizeof vmlinux_suffixes / sizeof vmlinux_suffixes[0];
+	 ++i)
+      {
+	char *zname;
+	if (asprintf (&zname, "%s%s", *fname, vmlinux_suffixes[i]) > 0)
+	  {
+	    fd = TEMP_FAILURE_RETRY (open (zname, O_RDONLY));
+	    if (fd < 0)
+	      free (zname);
+	    else
+	      {
+		free (*fname);
+		*fname = zname;
+	      }
+	  }
+      }
+
+  if (fd < 0)
+    {
+      free (*fname);
+      *fname = NULL;
+    }
+
+  return fd;
+}
+
+static inline const char *
+kernel_release (void)
+{
+#ifdef __linux__
+  /* Cache the `uname -r` string we'll use.  */
+  static struct utsname utsname;
+  if (utsname.release[0] == '\0' && uname (&utsname) != 0)
+    return NULL;
+  return utsname.release;
+#else
+  /* Used for finding the running linux kernel, which isn't supported
+     on non-linux kernel systems.  */
+  errno = ENOTSUP;
+  return NULL;
+#endif
+}
+
+static int
+find_kernel_elf (Dwfl *dwfl, const char *release, char **fname)
+{
+  if ((release[0] == '/'
+       ? asprintf (fname, "%s/vmlinux", release)
+       : asprintf (fname, "/boot/vmlinux-%s", release)) < 0)
+    return -1;
+
+  int fd = try_kernel_name (dwfl, fname, true);
+  if (fd < 0 && release[0] != '/')
+    {
+      free (*fname);
+      if (asprintf (fname, MODULEDIRFMT "/vmlinux", release) < 0)
+	return -1;
+      fd = try_kernel_name (dwfl, fname, true);
+    }
+
+  return fd;
+}
+
+static int
+get_release (Dwfl *dwfl, const char **release)
+{
+  if (dwfl == NULL)
+    return -1;
+
+  const char *release_string = release == NULL ? NULL : *release;
+  if (release_string == NULL)
+    {
+      release_string = kernel_release ();
+      if (release_string == NULL)
+	return errno;
+      if (release != NULL)
+	*release = release_string;
+    }
+
+  return 0;
+}
+
+static int
+report_kernel (Dwfl *dwfl, const char **release,
+	       int (*predicate) (const char *module, const char *file))
+{
+  int result = get_release (dwfl, release);
+  if (unlikely (result != 0))
+    return result;
+
+  char *fname;
+  int fd = find_kernel_elf (dwfl, *release, &fname);
+
+  if (fd < 0)
+    result = ((predicate != NULL && !(*predicate) (KERNEL_MODNAME, NULL))
+	      ? 0 : errno ?: ENOENT);
+  else
+    {
+      bool report = true;
+
+      if (predicate != NULL)
+	{
+	  /* Let the predicate decide whether to use this one.  */
+	  int want = (*predicate) (KERNEL_MODNAME, fname);
+	  if (want < 0)
+	    result = errno;
+	  report = want > 0;
+	}
+
+      if (report)
+	{
+	  /* Note that on some architectures (e.g. x86_64) the vmlinux
+	     is ET_EXEC, while on others (e.g. ppc64) it is ET_DYN.
+	     In both cases the phdr p_vaddr load address will be non-zero.
+	     We want the image to be placed as if it was ET_DYN, so
+	     pass true for add_p_vaddr which will do the right thing
+	     (in combination with a zero base) in either case.  */
+	  Dwfl_Module *mod = INTUSE(dwfl_report_elf) (dwfl, KERNEL_MODNAME,
+						      fname, fd, 0, true);
+	  if (mod == NULL)
+	    result = -1;
+	  else
+	    /* The kernel is ET_EXEC, but always treat it as relocatable.  */
+	    mod->e_type = ET_DYN;
+	}
+
+      free (fname);
+
+      if (!report || result < 0)
+	close (fd);
+    }
+
+  return result;
+}
+
+/* Look for a kernel debug archive.  If we find one, report all its modules.
+   If not, return ENOENT.  */
+static int
+report_kernel_archive (Dwfl *dwfl, const char **release,
+		       int (*predicate) (const char *module, const char *file))
+{
+  int result = get_release (dwfl, release);
+  if (unlikely (result != 0))
+    return result;
+
+  char *archive;
+  int res = (((*release)[0] == '/')
+	     ? asprintf (&archive, "%s/debug.a", *release)
+	     : asprintf (&archive, MODULEDIRFMT "/debug.a", *release));
+  if (unlikely (res < 0))
+    return ENOMEM;
+
+  int fd = try_kernel_name (dwfl, &archive, false);
+  if (fd < 0)
+    result = errno ?: ENOENT;
+  else
+    {
+      /* We have the archive file open!  */
+      Dwfl_Module *last = __libdwfl_report_offline (dwfl, NULL, archive, fd,
+						    true, predicate);
+      if (unlikely (last == NULL))
+	result = -1;
+      else
+	{
+	  /* Find the kernel and move it to the head of the list.  */
+	  Dwfl_Module **tailp = &dwfl->modulelist, **prevp = tailp;
+	  for (Dwfl_Module *m = *prevp; m != NULL; m = *(prevp = &m->next))
+	    if (!m->gc && m->e_type != ET_REL && !strcmp (m->name, "kernel"))
+	      {
+		*prevp = m->next;
+		m->next = *tailp;
+		*tailp = m;
+		break;
+	      }
+	}
+    }
+
+  free (archive);
+  return result;
+}
+
+static size_t
+check_suffix (const FTSENT *f, size_t namelen)
+{
+#define TRY(sfx)							\
+  if ((namelen ? f->fts_namelen == namelen + sizeof sfx - 1		\
+       : f->fts_namelen >= sizeof sfx)					\
+      && !memcmp (f->fts_name + f->fts_namelen - (sizeof sfx - 1),	\
+		  sfx, sizeof sfx))					\
+    return sizeof sfx - 1
+
+  TRY (".ko");
+  TRY (".ko.gz");
+#if USE_BZLIB
+  TRY (".ko.bz2");
+#endif
+#if USE_LZMA
+  TRY (".ko.xz");
+#endif
+
+  return 0;
+
+#undef	TRY
+}
+
+/* Report a kernel and all its modules found on disk, for offline use.
+   If RELEASE starts with '/', it names a directory to look in;
+   if not, it names a directory to find under /lib/modules/;
+   if null, /lib/modules/`uname -r` is used.
+   Returns zero on success, -1 if dwfl_report_module failed,
+   or an errno code if finding the files on disk failed.  */
+int
+dwfl_linux_kernel_report_offline (Dwfl *dwfl, const char *release,
+				  int (*predicate) (const char *module,
+						    const char *file))
+{
+  int result = report_kernel_archive (dwfl, &release, predicate);
+  if (result != ENOENT)
+    return result;
+
+  /* First report the kernel.  */
+  result = report_kernel (dwfl, &release, predicate);
+  if (result == 0)
+    {
+      /* Do "find /lib/modules/RELEASE -name *.ko".  */
+
+      char *modulesdir[] = { NULL, NULL };
+      if (release[0] == '/')
+	modulesdir[0] = (char *) release;
+      else
+	{
+	  if (asprintf (&modulesdir[0], MODULEDIRFMT, release) < 0)
+	    return errno;
+	}
+
+      FTS *fts = fts_open (modulesdir, FTS_NOSTAT | FTS_LOGICAL, NULL);
+      if (modulesdir[0] == (char *) release)
+	modulesdir[0] = NULL;
+      if (fts == NULL)
+	{
+	  free (modulesdir[0]);
+	  return errno;
+	}
+
+      FTSENT *f;
+      while ((f = fts_read (fts)) != NULL)
+	{
+	  /* Skip a "source" subtree, which tends to be large.
+	     This insane hard-coding of names is what depmod does too.  */
+	  if (f->fts_namelen == sizeof "source" - 1
+	      && !strcmp (f->fts_name, "source"))
+	    {
+	      fts_set (fts, f, FTS_SKIP);
+	      continue;
+	    }
+
+	  switch (f->fts_info)
+	    {
+	    case FTS_F:
+	    case FTS_SL:
+	    case FTS_NSOK:;
+	      /* See if this file name matches "*.ko".  */
+	      const size_t suffix = check_suffix (f, 0);
+	      if (suffix)
+		{
+		  /* We have a .ko file to report.  Following the algorithm
+		     by which the kernel makefiles set KBUILD_MODNAME, we
+		     replace all ',' or '-' with '_' in the file name and
+		     call that the module name.  Modules could well be
+		     built using different embedded names than their file
+		     names.  To handle that, we would have to look at the
+		     __this_module.name contents in the module's text.  */
+
+		  char *name = strndup (f->fts_name, f->fts_namelen - suffix);
+		  if (unlikely (name == NULL))
+		    {
+		      __libdwfl_seterrno (DWFL_E_NOMEM);
+		      result = -1;
+		      break;
+		    }
+		  for (size_t i = 0; i < f->fts_namelen - suffix; ++i)
+		    if (name[i] == '-' || name[i] == ',')
+		      name[i] = '_';
+
+		  if (predicate != NULL)
+		    {
+		      /* Let the predicate decide whether to use this one.  */
+		      int want = (*predicate) (name, f->fts_path);
+		      if (want < 0)
+			{
+			  result = -1;
+			  free (name);
+			  break;
+			}
+		      if (!want)
+			{
+			  free (name);
+			  continue;
+			}
+		    }
+
+		  if (dwfl_report_offline (dwfl, name, f->fts_path, -1) == NULL)
+		    {
+		      free (name);
+		      result = -1;
+		      break;
+		    }
+		  free (name);
+		}
+	      continue;
+
+	    case FTS_ERR:
+	    case FTS_DNR:
+	    case FTS_NS:
+	      result = f->fts_errno;
+	      break;
+
+	    case FTS_SLNONE:
+	    default:
+	      continue;
+	    }
+
+	  /* We only get here in error cases.  */
+	  break;
+	}
+      fts_close (fts);
+      free (modulesdir[0]);
+    }
+
+  return result;
+}
+INTDEF (dwfl_linux_kernel_report_offline)
+
+
+/* State of read_address used by intuit_kernel_bounds. */
+struct read_address_state {
+  FILE *f;
+  char *line;
+  size_t linesz;
+  size_t n;
+  char *p;
+  const char *type;
+};
+
+static inline bool
+read_address (struct read_address_state *state, Dwarf_Addr *addr)
+{
+  if ((state->n = getline (&state->line, &state->linesz, state->f)) < 1 ||
+      state->line[state->n - 2] == ']')
+    return false;
+  *addr = strtoull (state->line, &state->p, 16);
+  state->p += strspn (state->p, " \t");
+  state->type = strsep (&state->p, " \t\n");
+  if (state->type == NULL)
+    return false;
+  return state->p != NULL && state->p != state->line;
+}
+
+
+/* Grovel around to guess the bounds of the runtime kernel image.  */
+static int
+intuit_kernel_bounds (Dwarf_Addr *start, Dwarf_Addr *end, Dwarf_Addr *notes)
+{
+  struct read_address_state state = { NULL, NULL, 0, 0, NULL, NULL };
+
+  state.f = fopen (KSYMSFILE, "r");
+  if (state.f == NULL)
+    return errno;
+
+  (void) __fsetlocking (state.f, FSETLOCKING_BYCALLER);
+
+  *notes = 0;
+
+  int result;
+  do
+    result = read_address (&state, start) ? 0 : -1;
+  while (result == 0 && strchr ("TtRr", *state.type) == NULL);
+
+  if (result == 0)
+    {
+      *end = *start;
+      while (read_address (&state, end))
+	if (*notes == 0 && !strcmp (state.p, "__start_notes\n"))
+	  *notes = *end;
+
+      Dwarf_Addr round_kernel = sysconf (_SC_PAGESIZE);
+      *start &= -(Dwarf_Addr) round_kernel;
+      *end += round_kernel - 1;
+      *end &= -(Dwarf_Addr) round_kernel;
+      if (*start >= *end || *end - *start < round_kernel)
+	result = -1;
+    }
+  free (state.line);
+
+  if (result == -1)
+    result = ferror_unlocked (state.f) ? errno : ENOEXEC;
+
+  fclose (state.f);
+
+  return result;
+}
+
+
+/* Look for a build ID note in NOTESFILE and associate the ID with MOD.  */
+static int
+check_notes (Dwfl_Module *mod, const char *notesfile,
+	     Dwarf_Addr vaddr, const char *secname)
+{
+  int fd = open (notesfile, O_RDONLY);
+  if (fd < 0)
+    return 1;
+
+  assert (sizeof (Elf32_Nhdr) == sizeof (GElf_Nhdr));
+  assert (sizeof (Elf64_Nhdr) == sizeof (GElf_Nhdr));
+  union
+  {
+    GElf_Nhdr nhdr;
+    unsigned char data[8192];
+  } buf;
+
+  ssize_t n = read (fd, buf.data, sizeof buf);
+  close (fd);
+
+  if (n <= 0)
+    return 1;
+
+  unsigned char *p = buf.data;
+  while (p < &buf.data[n])
+    {
+      /* No translation required since we are reading the native kernel.  */
+      GElf_Nhdr *nhdr = (void *) p;
+      p += sizeof *nhdr;
+      unsigned char *name = p;
+      p += (nhdr->n_namesz + 3) & -4U;
+      unsigned char *bits = p;
+      p += (nhdr->n_descsz + 3) & -4U;
+
+      if (p <= &buf.data[n]
+	  && nhdr->n_type == NT_GNU_BUILD_ID
+	  && nhdr->n_namesz == sizeof "GNU"
+	  && !memcmp (name, "GNU", sizeof "GNU"))
+	{
+	  /* Found it.  For a module we must figure out its VADDR now.  */
+
+	  if (secname != NULL
+	      && (INTUSE(dwfl_linux_kernel_module_section_address)
+		  (mod, NULL, mod->name, 0, secname, 0, NULL, &vaddr) != 0
+		  || vaddr == (GElf_Addr) -1l))
+	    vaddr = 0;
+
+	  if (vaddr != 0)
+	    vaddr += bits - buf.data;
+	  return INTUSE(dwfl_module_report_build_id) (mod, bits,
+						      nhdr->n_descsz, vaddr);
+	}
+    }
+
+  return 0;
+}
+
+/* Look for a build ID for the kernel.  */
+static int
+check_kernel_notes (Dwfl_Module *kernelmod, GElf_Addr vaddr)
+{
+  return check_notes (kernelmod, KNOTESFILE, vaddr, NULL) < 0 ? -1 : 0;
+}
+
+/* Look for a build ID for a loaded kernel module.  */
+static int
+check_module_notes (Dwfl_Module *mod)
+{
+  char *dirs[2] = { NULL, NULL };
+  if (asprintf (&dirs[0], MODNOTESFMT, mod->name) < 0)
+    return ENOMEM;
+
+  FTS *fts = fts_open (dirs, FTS_NOSTAT | FTS_LOGICAL, NULL);
+  if (fts == NULL)
+    {
+      free (dirs[0]);
+      return 0;
+    }
+
+  int result = 0;
+  FTSENT *f;
+  while ((f = fts_read (fts)) != NULL)
+    {
+      switch (f->fts_info)
+	{
+	case FTS_F:
+	case FTS_SL:
+	case FTS_NSOK:
+	  result = check_notes (mod, f->fts_accpath, 0, f->fts_name);
+	  if (result > 0)	/* Nothing found.  */
+	    {
+	      result = 0;
+	      continue;
+	    }
+	  break;
+
+	case FTS_ERR:
+	case FTS_DNR:
+	  result = f->fts_errno;
+	  break;
+
+	case FTS_NS:
+	case FTS_SLNONE:
+	default:
+	  continue;
+	}
+
+      /* We only get here when finished or in error cases.  */
+      break;
+    }
+  fts_close (fts);
+  free (dirs[0]);
+
+  return result;
+}
+
+int
+dwfl_linux_kernel_report_kernel (Dwfl *dwfl)
+{
+  Dwarf_Addr start = 0;
+  Dwarf_Addr end = 0;
+
+  #define report() \
+    (INTUSE(dwfl_report_module) (dwfl, KERNEL_MODNAME, start, end))
+
+  /* This is a bit of a kludge.  If we already reported the kernel,
+     don't bother figuring it out again--it never changes.  */
+  for (Dwfl_Module *m = dwfl->modulelist; m != NULL; m = m->next)
+    if (!strcmp (m->name, KERNEL_MODNAME))
+      {
+	start = m->low_addr;
+	end = m->high_addr;
+	return report () == NULL ? -1 : 0;
+      }
+
+  /* Try to figure out the bounds of the kernel image without
+     looking for any vmlinux file.  */
+  Dwarf_Addr notes;
+  /* The compiler cannot deduce that if intuit_kernel_bounds returns
+     zero NOTES will be initialized.  Fake the initialization.  */
+  asm ("" : "=m" (notes));
+  int result = intuit_kernel_bounds (&start, &end, &notes);
+  if (result == 0)
+    {
+      Dwfl_Module *mod = report ();
+      return unlikely (mod == NULL) ? -1 : check_kernel_notes (mod, notes);
+    }
+  if (result != ENOENT)
+    return result;
+
+  /* Find the ELF file for the running kernel and dwfl_report_elf it.  */
+  return report_kernel (dwfl, NULL, NULL);
+}
+INTDEF (dwfl_linux_kernel_report_kernel)
+
+
+static inline bool
+subst_name (char from, char to,
+            const char * const module_name,
+            char * const alternate_name,
+            const size_t namelen)
+{
+  const char *n = memchr (module_name, from, namelen);
+  if (n == NULL)
+    return false;
+  char *a = mempcpy (alternate_name, module_name, n - module_name);
+  *a++ = to;
+  ++n;
+  const char *p;
+  while ((p = memchr (n, from, namelen - (n - module_name))) != NULL)
+    {
+      a = mempcpy (a, n, p - n);
+      *a++ = to;
+      n = p + 1;
+    }
+  memcpy (a, n, namelen - (n - module_name) + 1);
+  return true;
+}
+
+/* Dwfl_Callbacks.find_elf for the running Linux kernel and its modules.  */
+
+int
+dwfl_linux_kernel_find_elf (Dwfl_Module *mod,
+			    void **userdata __attribute__ ((unused)),
+			    const char *module_name,
+			    Dwarf_Addr base __attribute__ ((unused)),
+			    char **file_name, Elf **elfp)
+{
+  if (mod->build_id_len > 0)
+    {
+      int fd = INTUSE(dwfl_build_id_find_elf) (mod, NULL, NULL, 0,
+					       file_name, elfp);
+      if (fd >= 0 || mod->main.elf != NULL || errno != 0)
+	return fd;
+    }
+
+  const char *release = kernel_release ();
+  if (release == NULL)
+    return errno;
+
+  if (!strcmp (module_name, KERNEL_MODNAME))
+    return find_kernel_elf (mod->dwfl, release, file_name);
+
+  /* Do "find /lib/modules/`uname -r` -name MODULE_NAME.ko".  */
+
+  char *modulesdir[] = { NULL, NULL };
+  if (asprintf (&modulesdir[0], MODULEDIRFMT, release) < 0)
+    return -1;
+
+  FTS *fts = fts_open (modulesdir, FTS_NOSTAT | FTS_LOGICAL, NULL);
+  if (fts == NULL)
+    {
+      free (modulesdir[0]);
+      return -1;
+    }
+
+  size_t namelen = strlen (module_name);
+
+  /* This is a kludge.  There is no actual necessary relationship between
+     the name of the .ko file installed and the module name the kernel
+     knows it by when it's loaded.  The kernel's only idea of the module
+     name comes from the name embedded in the object's magic
+     .gnu.linkonce.this_module section.
+
+     In practice, these module names match the .ko file names except for
+     some using '_' and some using '-'.  So our cheap kludge is to look for
+     two files when either a '_' or '-' appears in a module name, one using
+     only '_' and one only using '-'.  */
+
+  char *alternate_name = malloc (namelen + 1);
+  if (unlikely (alternate_name == NULL))
+    {
+      free (modulesdir[0]);
+      return ENOMEM;
+    }
+  if (!subst_name ('-', '_', module_name, alternate_name, namelen) &&
+      !subst_name ('_', '-', module_name, alternate_name, namelen))
+    alternate_name[0] = '\0';
+
+  FTSENT *f;
+  int error = ENOENT;
+  while ((f = fts_read (fts)) != NULL)
+    {
+      /* Skip a "source" subtree, which tends to be large.
+	 This insane hard-coding of names is what depmod does too.  */
+      if (f->fts_namelen == sizeof "source" - 1
+	  && !strcmp (f->fts_name, "source"))
+	{
+	  fts_set (fts, f, FTS_SKIP);
+	  continue;
+	}
+
+      error = ENOENT;
+      switch (f->fts_info)
+	{
+	case FTS_F:
+	case FTS_SL:
+	case FTS_NSOK:
+	  /* See if this file name is "MODULE_NAME.ko".  */
+	  if (check_suffix (f, namelen)
+	      && (!memcmp (f->fts_name, module_name, namelen)
+		  || !memcmp (f->fts_name, alternate_name, namelen)))
+	    {
+	      int fd = open (f->fts_accpath, O_RDONLY);
+	      *file_name = strdup (f->fts_path);
+	      fts_close (fts);
+	      free (modulesdir[0]);
+	      free (alternate_name);
+	      if (fd < 0)
+		free (*file_name);
+	      else if (*file_name == NULL)
+		{
+		  close (fd);
+		  fd = -1;
+		}
+	      return fd;
+	    }
+	  break;
+
+	case FTS_ERR:
+	case FTS_DNR:
+	case FTS_NS:
+	  error = f->fts_errno;
+	  break;
+
+	case FTS_SLNONE:
+	default:
+	  break;
+	}
+    }
+
+  fts_close (fts);
+  free (modulesdir[0]);
+  free (alternate_name);
+  errno = error;
+  return -1;
+}
+INTDEF (dwfl_linux_kernel_find_elf)
+
+
+/* Dwfl_Callbacks.section_address for kernel modules in the running Linux.
+   We read the information from /sys/module directly.  */
+
+int
+dwfl_linux_kernel_module_section_address
+(Dwfl_Module *mod __attribute__ ((unused)),
+ void **userdata __attribute__ ((unused)),
+ const char *modname, Dwarf_Addr base __attribute__ ((unused)),
+ const char *secname, Elf32_Word shndx __attribute__ ((unused)),
+ const GElf_Shdr *shdr __attribute__ ((unused)),
+ Dwarf_Addr *addr)
+{
+  char *sysfile;
+  if (asprintf (&sysfile, SECADDRDIRFMT "%s", modname, secname) < 0)
+    return DWARF_CB_ABORT;
+
+  FILE *f = fopen (sysfile, "r");
+  free (sysfile);
+
+  if (f == NULL)
+    {
+      if (errno == ENOENT)
+	{
+	  /* The .modinfo and .data.percpu sections are never kept
+	     loaded in the kernel.  If the kernel was compiled without
+	     CONFIG_MODULE_UNLOAD, the .exit.* sections are not
+	     actually loaded at all.
+
+	     Setting *ADDR to -1 tells the caller this section is
+	     actually absent from memory.  */
+
+	  if (!strcmp (secname, ".modinfo")
+	      || !strcmp (secname, ".data.percpu")
+	      || !strncmp (secname, ".exit", 5))
+	    {
+	      *addr = (Dwarf_Addr) -1l;
+	      return DWARF_CB_OK;
+	    }
+
+	  /* The goofy PPC64 module_frob_arch_sections function tweaks
+	     the section names as a way to control other kernel code's
+	     behavior, and this cruft leaks out into the /sys information.
+	     The file name for ".init*" may actually look like "_init*".  */
+
+	  const bool is_init = !strncmp (secname, ".init", 5);
+	  if (is_init)
+	    {
+	      if (asprintf (&sysfile, SECADDRDIRFMT "_%s",
+			    modname, &secname[1]) < 0)
+		return ENOMEM;
+	      f = fopen (sysfile, "r");
+	      free (sysfile);
+	      if (f != NULL)
+		goto ok;
+	    }
+
+	  /* The kernel truncates section names to MODULE_SECT_NAME_LEN - 1.
+	     In case that size increases in the future, look for longer
+	     truncated names first.  */
+	  size_t namelen = strlen (secname);
+	  if (namelen >= MODULE_SECT_NAME_LEN)
+	    {
+	      int len = asprintf (&sysfile, SECADDRDIRFMT "%s",
+				  modname, secname);
+	      if (len < 0)
+		return DWARF_CB_ABORT;
+	      char *end = sysfile + len;
+	      do
+		{
+		  *--end = '\0';
+		  f = fopen (sysfile, "r");
+		  if (is_init && f == NULL && errno == ENOENT)
+		    {
+		      sysfile[len - namelen] = '_';
+		      f = fopen (sysfile, "r");
+		      sysfile[len - namelen] = '.';
+		    }
+		}
+	      while (f == NULL && errno == ENOENT
+		     && end - &sysfile[len - namelen] >= MODULE_SECT_NAME_LEN);
+	      free (sysfile);
+
+	      if (f != NULL)
+		goto ok;
+	    }
+	}
+
+      return DWARF_CB_ABORT;
+    }
+
+ ok:
+  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
+
+  int result = (fscanf (f, "%" PRIx64 "\n", addr) == 1 ? 0
+		: ferror_unlocked (f) ? errno : ENOEXEC);
+  fclose (f);
+
+  if (result == 0)
+    return DWARF_CB_OK;
+
+  errno = result;
+  return DWARF_CB_ABORT;
+}
+INTDEF (dwfl_linux_kernel_module_section_address)
+
+int
+dwfl_linux_kernel_report_modules (Dwfl *dwfl)
+{
+  FILE *f = fopen (MODULELIST, "r");
+  if (f == NULL)
+    return errno;
+
+  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
+
+  int result = 0;
+  Dwarf_Addr modaddr;
+  unsigned long int modsz;
+  char modname[128];
+  char *line = NULL;
+  size_t linesz = 0;
+  /* We can't just use fscanf here because it's not easy to distinguish \n
+     from other whitespace so as to take the optional word following the
+     address but always stop at the end of the line.  */
+  while (getline (&line, &linesz, f) > 0
+	 && sscanf (line, "%128s %lu %*s %*s %*s %" PRIx64 " %*s\n",
+		    modname, &modsz, &modaddr) == 3)
+    {
+      Dwfl_Module *mod = INTUSE(dwfl_report_module) (dwfl, modname,
+						     modaddr, modaddr + modsz);
+      if (mod == NULL)
+	{
+	  result = -1;
+	  break;
+	}
+
+      result = check_module_notes (mod);
+    }
+  free (line);
+
+  if (result == 0)
+    result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC;
+
+  fclose (f);
+
+  return result;
+}
+INTDEF (dwfl_linux_kernel_report_modules)