Blame libdwfl/linux-proc-maps.c

Packit 032894
/* Standard libdwfl callbacks for debugging a live Linux process.
Packit 032894
   Copyright (C) 2005-2010, 2013, 2014, 2016 Red Hat, Inc.
Packit 032894
   This file is part of elfutils.
Packit 032894
Packit 032894
   This file is free software; you can redistribute it and/or modify
Packit 032894
   it under the terms of either
Packit 032894
Packit 032894
     * the GNU Lesser General Public License as published by the Free
Packit 032894
       Software Foundation; either version 3 of the License, or (at
Packit 032894
       your option) any later version
Packit 032894
Packit 032894
   or
Packit 032894
Packit 032894
     * the GNU General Public License as published by the Free
Packit 032894
       Software Foundation; either version 2 of the License, or (at
Packit 032894
       your option) any later version
Packit 032894
Packit 032894
   or both in parallel, as here.
Packit 032894
Packit 032894
   elfutils is distributed in the hope that it will be useful, but
Packit 032894
   WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 032894
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 032894
   General Public License for more details.
Packit 032894
Packit 032894
   You should have received copies of the GNU General Public License and
Packit 032894
   the GNU Lesser General Public License along with this program.  If
Packit 032894
   not, see <http://www.gnu.org/licenses/>.  */
Packit 032894
Packit 032894
#ifdef HAVE_CONFIG_H
Packit 032894
# include <config.h>
Packit 032894
#endif
Packit 032894
Packit 032894
#include "libdwflP.h"
Packit 032894
#include <inttypes.h>
Packit 032894
#include <sys/types.h>
Packit 032894
#include <sys/stat.h>
Packit 032894
#include <errno.h>
Packit 032894
#include <stdio.h>
Packit 032894
#include <stdio_ext.h>
Packit 032894
#include <stdbool.h>
Packit 032894
#include <string.h>
Packit 032894
#include <stdlib.h>
Packit 032894
#include <fcntl.h>
Packit 032894
#include <unistd.h>
Packit 032894
#include <assert.h>
Packit 032894
#include <endian.h>
Packit 032894
#include "system.h"
Packit 032894
Packit 032894
Packit 032894
#define PROCMAPSFMT	"/proc/%d/maps"
Packit 032894
#define PROCMEMFMT	"/proc/%d/mem"
Packit 032894
#define PROCAUXVFMT	"/proc/%d/auxv"
Packit 032894
#define PROCEXEFMT	"/proc/%d/exe"
Packit 032894
Packit 032894
Packit 032894
/* Return ELFCLASS64 or ELFCLASS32 for the main ELF executable.  Return
Packit 032894
   ELFCLASSNONE for an error.  */
Packit 032894
Packit 032894
static unsigned char
Packit 032894
get_pid_class (pid_t pid)
Packit 032894
{
Packit 032894
  char *fname;
Packit 032894
  if (asprintf (&fname, PROCEXEFMT, pid) < 0)
Packit 032894
    return ELFCLASSNONE;
Packit 032894
Packit 032894
  int fd = open (fname, O_RDONLY);
Packit 032894
  free (fname);
Packit 032894
  if (fd < 0)
Packit 032894
    return ELFCLASSNONE;
Packit 032894
Packit 032894
  unsigned char buf[EI_CLASS + 1];
Packit 032894
  ssize_t nread = pread_retry (fd, &buf, sizeof buf, 0);
Packit 032894
  close (fd);
Packit 032894
  if (nread != sizeof buf || buf[EI_MAG0] != ELFMAG0
Packit 032894
      || buf[EI_MAG1] != ELFMAG1 || buf[EI_MAG2] != ELFMAG2
Packit 032894
      || buf[EI_MAG3] != ELFMAG3
Packit 032894
      || (buf[EI_CLASS] != ELFCLASS64 && buf[EI_CLASS] != ELFCLASS32))
Packit 032894
    return ELFCLASSNONE;
Packit 032894
Packit 032894
  return buf[EI_CLASS];
Packit 032894
}
Packit 032894
Packit 032894
/* Search /proc/PID/auxv for the AT_SYSINFO_EHDR tag.
Packit 032894
Packit 032894
   It would be easiest to call get_pid_class and parse everything according to
Packit 032894
   the 32-bit or 64-bit class.  But this would bring the overhead of syscalls
Packit 032894
   to open and read the "/proc/%d/exe" file.
Packit 032894
Packit 032894
   Therefore this function tries to parse the "/proc/%d/auxv" content both
Packit 032894
   ways, as if it were the 32-bit format and also if it were the 64-bit format.
Packit 032894
   Only if it gives some valid data in both cases get_pid_class gets called.
Packit 032894
   In most cases only one of the format bit sizes gives valid data and the
Packit 032894
   get_pid_class call overhead can be saved.  */
Packit 032894
Packit 032894
static int
Packit 032894
grovel_auxv (pid_t pid, Dwfl *dwfl, GElf_Addr *sysinfo_ehdr)
Packit 032894
{
Packit 032894
  char *fname;
Packit 032894
  if (asprintf (&fname, PROCAUXVFMT, pid) < 0)
Packit 032894
    return ENOMEM;
Packit 032894
Packit 032894
  int fd = open (fname, O_RDONLY);
Packit 032894
  free (fname);
Packit 032894
  if (fd < 0)
Packit 032894
    return errno == ENOENT ? 0 : errno;
Packit 032894
Packit 032894
  GElf_Addr sysinfo_ehdr64 = 0;
Packit 032894
  GElf_Addr sysinfo_ehdr32 = 0;
Packit 032894
  GElf_Addr segment_align64 = dwfl->segment_align;
Packit 032894
  GElf_Addr segment_align32 = dwfl->segment_align;
Packit 032894
  off_t offset = 0;
Packit 032894
  ssize_t nread;
Packit 032894
  union
Packit 032894
  {
Packit 032894
    Elf64_auxv_t a64[64];
Packit 032894
    Elf32_auxv_t a32[128];
Packit 032894
  } d;
Packit 032894
  do
Packit 032894
    {
Packit 032894
      eu_static_assert (sizeof d.a64 == sizeof d.a32);
Packit 032894
      nread = pread_retry (fd, d.a64, sizeof d.a64, offset);
Packit 032894
      if (nread < 0)
Packit 032894
	{
Packit 032894
	  int ret = errno;
Packit 032894
	  close (fd);
Packit 032894
	  return ret;
Packit 032894
	}
Packit 032894
      for (size_t a32i = 0; a32i < nread / sizeof d.a32[0]; a32i++)
Packit 032894
	{
Packit 032894
	  const Elf32_auxv_t *a32 = d.a32 + a32i;
Packit 032894
	  switch (a32->a_type)
Packit 032894
	  {
Packit 032894
	    case AT_SYSINFO_EHDR:
Packit 032894
	      sysinfo_ehdr32 = a32->a_un.a_val;
Packit 032894
	      break;
Packit 032894
	    case AT_PAGESZ:
Packit 032894
	      segment_align32 = a32->a_un.a_val;
Packit 032894
	      break;
Packit 032894
	  }
Packit 032894
	}
Packit 032894
      for (size_t a64i = 0; a64i < nread / sizeof d.a64[0]; a64i++)
Packit 032894
	{
Packit 032894
	  const Elf64_auxv_t *a64 = d.a64 + a64i;
Packit 032894
	  switch (a64->a_type)
Packit 032894
	  {
Packit 032894
	    case AT_SYSINFO_EHDR:
Packit 032894
	      sysinfo_ehdr64 = a64->a_un.a_val;
Packit 032894
	      break;
Packit 032894
	    case AT_PAGESZ:
Packit 032894
	      segment_align64 = a64->a_un.a_val;
Packit 032894
	      break;
Packit 032894
	  }
Packit 032894
	}
Packit 032894
      offset += nread;
Packit 032894
    }
Packit 032894
  while (nread == sizeof d.a64);
Packit 032894
Packit 032894
  close (fd);
Packit 032894
Packit 032894
  bool valid64 = sysinfo_ehdr64 != 0 || segment_align64 != dwfl->segment_align;
Packit 032894
  bool valid32 = sysinfo_ehdr32 != 0 || segment_align32 != dwfl->segment_align;
Packit 032894
Packit 032894
  unsigned char pid_class = ELFCLASSNONE;
Packit 032894
  if (valid64 && valid32)
Packit 032894
    pid_class = get_pid_class (pid);
Packit 032894
Packit 032894
  if (pid_class == ELFCLASS64 || (valid64 && ! valid32))
Packit 032894
    {
Packit 032894
      *sysinfo_ehdr = sysinfo_ehdr64;
Packit 032894
      dwfl->segment_align = segment_align64;
Packit 032894
      return 0;
Packit 032894
    }
Packit 032894
  if (pid_class == ELFCLASS32 || (! valid64 && valid32))
Packit 032894
    {
Packit 032894
      *sysinfo_ehdr = sysinfo_ehdr32;
Packit 032894
      dwfl->segment_align = segment_align32;
Packit 032894
      return 0;
Packit 032894
    }
Packit 032894
  return ENOEXEC;
Packit 032894
}
Packit 032894
Packit 032894
static inline bool
Packit 032894
do_report (Dwfl *dwfl, char **plast_file, Dwarf_Addr low, Dwarf_Addr high)
Packit 032894
{
Packit 032894
  if (*plast_file != NULL)
Packit 032894
    {
Packit 032894
      Dwfl_Module *mod = INTUSE(dwfl_report_module) (dwfl, *plast_file,
Packit 032894
						     low, high);
Packit 032894
      free (*plast_file);
Packit 032894
      *plast_file = NULL;
Packit 032894
      if (unlikely (mod == NULL))
Packit 032894
        return true;
Packit 032894
    }
Packit 032894
  return false;
Packit 032894
}
Packit 032894
Packit 032894
#define report() do_report(dwfl, &last_file, low, high)
Packit 032894
Packit 032894
static int
Packit 032894
proc_maps_report (Dwfl *dwfl, FILE *f, GElf_Addr sysinfo_ehdr, pid_t pid)
Packit 032894
{
Packit 032894
  unsigned int last_dmajor = -1, last_dminor = -1;
Packit 032894
  uint64_t last_ino = -1;
Packit 032894
  char *last_file = NULL;
Packit 032894
  Dwarf_Addr low = 0, high = 0;
Packit 032894
Packit 032894
  char *line = NULL;
Packit 032894
  size_t linesz;
Packit 032894
  ssize_t len;
Packit 032894
  while ((len = getline (&line, &linesz, f)) > 0)
Packit 032894
    {
Packit 032894
      if (line[len - 1] == '\n')
Packit 032894
	line[len - 1] = '\0';
Packit 032894
Packit 032894
      Dwarf_Addr start, end, offset;
Packit 032894
      unsigned int dmajor, dminor;
Packit 032894
      uint64_t ino;
Packit 032894
      int nread = -1;
Packit 032894
      if (sscanf (line, "%" PRIx64 "-%" PRIx64 " %*s %" PRIx64
Packit 032894
		  " %x:%x %" PRIu64 " %n",
Packit 032894
		  &start, &end, &offset, &dmajor, &dminor, &ino, &nread) < 6
Packit 032894
	  || nread <= 0)
Packit 032894
	{
Packit 032894
	  free (line);
Packit 032894
	  free (last_file);
Packit 032894
	  return ENOEXEC;
Packit 032894
	}
Packit 032894
Packit 032894
      /* If this is the special mapping AT_SYSINFO_EHDR pointed us at,
Packit 032894
	 report the last one and then this special one.  */
Packit 032894
      if (start == sysinfo_ehdr && start != 0)
Packit 032894
	{
Packit 032894
	  if (report ())
Packit 032894
	    {
Packit 032894
	    bad_report:
Packit 032894
	      free (line);
Packit 032894
	      return -1;
Packit 032894
	    }
Packit 032894
Packit 032894
	  low = start;
Packit 032894
	  high = end;
Packit 032894
	  if (asprintf (&last_file, "[vdso: %d]", (int) pid) < 0
Packit 032894
	      || report ())
Packit 032894
	    goto bad_report;
Packit 032894
	}
Packit 032894
Packit 032894
      char *file = line + nread + strspn (line + nread, " \t");
Packit 032894
      if (file[0] != '/' || (ino == 0 && dmajor == 0 && dminor == 0))
Packit 032894
	/* This line doesn't indicate a file mapping.  */
Packit 032894
	continue;
Packit 032894
Packit 032894
      if (last_file != NULL
Packit 032894
	  && ino == last_ino && dmajor == last_dmajor && dminor == last_dminor)
Packit 032894
	{
Packit 032894
	  /* This is another portion of the same file's mapping.  */
Packit 032894
	  if (strcmp (last_file, file) != 0)
Packit 032894
	    {
Packit 032894
	      free (last_file);
Packit 032894
	      goto bad_report;
Packit 032894
	    }
Packit 032894
	  high = end;
Packit 032894
	}
Packit 032894
      else
Packit 032894
	{
Packit 032894
	  /* This is a different file mapping.  Report the last one.  */
Packit 032894
	  if (report ())
Packit 032894
	    goto bad_report;
Packit 032894
	  low = start;
Packit 032894
	  high = end;
Packit 032894
	  last_file = strdup (file);
Packit 032894
	  last_ino = ino;
Packit 032894
	  last_dmajor = dmajor;
Packit 032894
	  last_dminor = dminor;
Packit 032894
	}
Packit 032894
    }
Packit 032894
  free (line);
Packit 032894
Packit 032894
  int result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC;
Packit 032894
Packit 032894
  /* Report the final one.  */
Packit 032894
  bool lose = report ();
Packit 032894
Packit 032894
  return result != 0 ? result : lose ? -1 : 0;
Packit 032894
}
Packit 032894
Packit 032894
int
Packit 032894
dwfl_linux_proc_maps_report (Dwfl *dwfl, FILE *f)
Packit 032894
{
Packit 032894
  return proc_maps_report (dwfl, f, 0, 0);
Packit 032894
}
Packit 032894
INTDEF (dwfl_linux_proc_maps_report)
Packit 032894
Packit 032894
int
Packit 032894
dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid)
Packit 032894
{
Packit 032894
  if (dwfl == NULL)
Packit 032894
    return -1;
Packit 032894
Packit 032894
  /* We'll notice the AT_SYSINFO_EHDR address specially when we hit it.  */
Packit 032894
  GElf_Addr sysinfo_ehdr = 0;
Packit 032894
  int result = grovel_auxv (pid, dwfl, &sysinfo_ehdr);
Packit 032894
  if (result != 0)
Packit 032894
    return result;
Packit 032894
Packit 032894
  char *fname;
Packit 032894
  if (asprintf (&fname, PROCMAPSFMT, pid) < 0)
Packit 032894
    return ENOMEM;
Packit 032894
Packit 032894
  FILE *f = fopen (fname, "r");
Packit 032894
  free (fname);
Packit 032894
  if (f == NULL)
Packit 032894
    return errno;
Packit 032894
Packit 032894
  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
Packit 032894
Packit 032894
  result = proc_maps_report (dwfl, f, sysinfo_ehdr, pid);
Packit 032894
Packit 032894
  fclose (f);
Packit 032894
Packit 032894
  return result;
Packit 032894
}
Packit 032894
INTDEF (dwfl_linux_proc_report)
Packit 032894
Packit 032894
static ssize_t
Packit 032894
read_proc_memory (void *arg, void *data, GElf_Addr address,
Packit 032894
		  size_t minread, size_t maxread)
Packit 032894
{
Packit 032894
  const int fd = *(const int *) arg;
Packit 032894
Packit 032894
  /* This code relies on the fact the Linux kernel accepts negative
Packit 032894
     offsets when seeking /dev/$$/mem files, as a special case. In
Packit 032894
     particular pread cannot be used here, because it will always
Packit 032894
     return EINVAL when passed a negative offset.  */
Packit 032894
Packit 032894
  if (lseek (fd, (off_t) address, SEEK_SET) == -1)
Packit 032894
    return -1;
Packit 032894
Packit 032894
  ssize_t nread = read (fd, data, maxread);
Packit 032894
Packit 032894
  if (nread > 0 && (size_t) nread < minread)
Packit 032894
    nread = 0;
Packit 032894
  return nread;
Packit 032894
}
Packit 032894
Packit 032894
extern Elf *elf_from_remote_memory (GElf_Addr ehdr_vma,
Packit 032894
				    GElf_Xword pagesize,
Packit 032894
				    GElf_Addr *loadbasep,
Packit 032894
				    ssize_t (*read_memory) (void *arg,
Packit 032894
							    void *data,
Packit 032894
							    GElf_Addr address,
Packit 032894
							    size_t minread,
Packit 032894
							    size_t maxread),
Packit 032894
				    void *arg);
Packit 032894
Packit 032894
Packit 032894
/* Dwfl_Callbacks.find_elf */
Packit 032894
Packit 032894
int
Packit 032894
dwfl_linux_proc_find_elf (Dwfl_Module *mod __attribute__ ((unused)),
Packit 032894
			  void **userdata __attribute__ ((unused)),
Packit 032894
			  const char *module_name, Dwarf_Addr base,
Packit 032894
			  char **file_name, Elf **elfp)
Packit 032894
{
Packit 032894
  int pid = -1;
Packit 032894
  if (module_name[0] == '/')
Packit 032894
    {
Packit 032894
      /* When this callback is used together with dwfl_linux_proc_report
Packit 032894
	 then we might see mappings of special character devices.  Make
Packit 032894
	 sure we only open and return regular files.  Special devices
Packit 032894
	 might hang on open or read.  (deleted) files are super special.
Packit 032894
	 The image might come from memory if we are attached.  */
Packit 032894
      struct stat sb;
Packit 032894
      if (stat (module_name, &sb) == -1 || (sb.st_mode & S_IFMT) != S_IFREG)
Packit 032894
	{
Packit 032894
	  if (strcmp (strrchr (module_name, ' ') ?: "", " (deleted)") == 0)
Packit 032894
	    pid = INTUSE(dwfl_pid) (mod->dwfl);
Packit 032894
	  else
Packit 032894
	    return -1;
Packit 032894
	}
Packit 032894
Packit 032894
      if (pid == -1)
Packit 032894
	{
Packit 032894
	  int fd = open (module_name, O_RDONLY);
Packit 032894
	  if (fd >= 0)
Packit 032894
	    {
Packit 032894
	      *file_name = strdup (module_name);
Packit 032894
	      if (*file_name == NULL)
Packit 032894
		{
Packit 032894
		  close (fd);
Packit 032894
		  return ENOMEM;
Packit 032894
		}
Packit 032894
	    }
Packit 032894
	  return fd;
Packit 032894
	}
Packit 032894
    }
Packit 032894
Packit 032894
  if (pid != -1 || sscanf (module_name, "[vdso: %d]", &pid) == 1)
Packit 032894
    {
Packit 032894
      /* Special case for in-memory ELF image.  */
Packit 032894
Packit 032894
      bool detach = false;
Packit 032894
      bool tid_was_stopped = false;
Packit 032894
      struct __libdwfl_pid_arg *pid_arg = __libdwfl_get_pid_arg (mod->dwfl);
Packit 032894
      if (pid_arg != NULL && ! pid_arg->assume_ptrace_stopped)
Packit 032894
	{
Packit 032894
	  /* If any thread is already attached we are fine.  Read
Packit 032894
	     through that thread.  It doesn't have to be the main
Packit 032894
	     thread pid.  */
Packit 032894
	  pid_t tid = pid_arg->tid_attached;
Packit 032894
	  if (tid != 0)
Packit 032894
	    pid = tid;
Packit 032894
	  else
Packit 032894
	    detach = __libdwfl_ptrace_attach (pid, &tid_was_stopped);
Packit 032894
	}
Packit 032894
Packit 032894
      char *fname;
Packit 032894
      if (asprintf (&fname, PROCMEMFMT, pid) < 0)
Packit 032894
	goto detach;
Packit 032894
Packit 032894
      int fd = open (fname, O_RDONLY);
Packit 032894
      free (fname);
Packit 032894
      if (fd < 0)
Packit 032894
	goto detach;
Packit 032894
Packit 032894
      *elfp = elf_from_remote_memory (base, sysconf (_SC_PAGESIZE), NULL,
Packit 032894
				      &read_proc_memory, &fd;;
Packit 032894
Packit 032894
      close (fd);
Packit 032894
Packit 032894
      *file_name = NULL;
Packit 032894
Packit 032894
    detach:
Packit 032894
      if (detach)
Packit 032894
	__libdwfl_ptrace_detach (pid, tid_was_stopped);
Packit 032894
      return -1;
Packit 032894
    }
Packit 032894
Packit 032894
  return -1;
Packit 032894
}
Packit 032894
INTDEF (dwfl_linux_proc_find_elf)