1/* Standard libdwfl callbacks for debugging a live Linux process.
2   Copyright (C) 2005, 2007, 2008 Red Hat, Inc.
3   This file is part of Red Hat elfutils.
4
5   Red Hat elfutils is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by the
7   Free Software Foundation; version 2 of the License.
8
9   Red Hat elfutils is distributed in the hope that it will be useful, but
10   WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   General Public License for more details.
13
14   You should have received a copy of the GNU General Public License along
15   with Red Hat elfutils; if not, write to the Free Software Foundation,
16   Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA.
17
18   In addition, as a special exception, Red Hat, Inc. gives You the
19   additional right to link the code of Red Hat elfutils with code licensed
20   under any Open Source Initiative certified open source license
21   (http://www.opensource.org/licenses/index.php) which requires the
22   distribution of source code with any binary distribution and to
23   distribute linked combinations of the two.  Non-GPL Code permitted under
24   this exception must only link to the code of Red Hat elfutils through
25   those well defined interfaces identified in the file named EXCEPTION
26   found in the source code files (the "Approved Interfaces").  The files
27   of Non-GPL Code may instantiate templates or use macros or inline
28   functions from the Approved Interfaces without causing the resulting
29   work to be covered by the GNU General Public License.  Only Red Hat,
30   Inc. may make changes or additions to the list of Approved Interfaces.
31   Red Hat's grant of this exception is conditioned upon your not adding
32   any new exceptions.  If you wish to add a new Approved Interface or
33   exception, please contact Red Hat.  You must obey the GNU General Public
34   License in all respects for all of the Red Hat elfutils code and other
35   code used in conjunction with Red Hat elfutils except the Non-GPL Code
36   covered by this exception.  If you modify this file, you may extend this
37   exception to your version of the file, but you are not obligated to do
38   so.  If you do not wish to provide this exception without modification,
39   you must delete this exception statement from your version and license
40   this file solely under the GPL without exception.
41
42   Red Hat elfutils is an included package of the Open Invention Network.
43   An included package of the Open Invention Network is a package for which
44   Open Invention Network licensees cross-license their patents.  No patent
45   license is granted, either expressly or impliedly, by designation as an
46   included package.  Should you wish to participate in the Open Invention
47   Network licensing program, please visit www.openinventionnetwork.com
48   <http://www.openinventionnetwork.com>.  */
49
50#include "libdwflP.h"
51#include <inttypes.h>
52#include <sys/types.h>
53#include <errno.h>
54#include <stdio.h>
55#include <stdio_ext.h>
56#include <stdbool.h>
57#include <string.h>
58#include <stdlib.h>
59#include <fcntl.h>
60#include <unistd.h>
61#include <assert.h>
62#include <endian.h>
63
64
65#define PROCMAPSFMT	"/proc/%d/maps"
66#define PROCMEMFMT	"/proc/%d/mem"
67#define PROCAUXVFMT	"/proc/%d/auxv"
68
69
70/* Search /proc/PID/auxv for the AT_SYSINFO_EHDR tag.  */
71
72static int
73find_sysinfo_ehdr (pid_t pid, GElf_Addr *sysinfo_ehdr)
74{
75  char *fname;
76  if (asprintf (&fname, PROCAUXVFMT, pid) < 0)
77    return ENOMEM;
78
79  int fd = open64 (fname, O_RDONLY);
80  free (fname);
81  if (fd < 0)
82    return errno == ENOENT ? 0 : errno;
83
84  ssize_t nread;
85  do
86    {
87      union
88      {
89	char buffer[sizeof (long int) * 2 * 64];
90	Elf64_auxv_t a64[sizeof (long int) * 2 * 64 / sizeof (Elf64_auxv_t)];
91	Elf32_auxv_t a32[sizeof (long int) * 2 * 32 / sizeof (Elf32_auxv_t)];
92      } d;
93      nread = read (fd, &d, sizeof d);
94      if (nread > 0)
95	{
96	  switch (sizeof (long int))
97	    {
98	    case 4:
99	      for (size_t i = 0; (char *) &d.a32[i] < &d.buffer[nread]; ++i)
100		if (d.a32[i].a_type == AT_SYSINFO_EHDR)
101		  {
102		    *sysinfo_ehdr = d.a32[i].a_un.a_val;
103		    nread = 0;
104		    break;
105		  }
106	      break;
107	    case 8:
108	      for (size_t i = 0; (char *) &d.a64[i] < &d.buffer[nread]; ++i)
109		if (d.a64[i].a_type == AT_SYSINFO_EHDR)
110		  {
111		    *sysinfo_ehdr = d.a64[i].a_un.a_val;
112		    nread = 0;
113		    break;
114		  }
115	      break;
116	    default:
117	      abort ();
118	      break;
119	    }
120	}
121    }
122  while (nread > 0);
123
124  close (fd);
125
126  return nread < 0 ? errno : 0;
127}
128
129static int
130proc_maps_report (Dwfl *dwfl, FILE *f, GElf_Addr sysinfo_ehdr, pid_t pid)
131{
132  unsigned int last_dmajor = -1, last_dminor = -1;
133  uint64_t last_ino = -1;
134  char *last_file = NULL;
135  Dwarf_Addr low = 0, high = 0;
136
137  inline bool report (void)
138    {
139      if (last_file != NULL)
140	{
141	  Dwfl_Module *mod = INTUSE(dwfl_report_module) (dwfl, last_file,
142							 low, high);
143	  free (last_file);
144	  last_file = NULL;
145	  if (unlikely (mod == NULL))
146	    return true;
147	}
148      return false;
149    }
150
151  char *line = NULL;
152  size_t linesz;
153  ssize_t len;
154  while ((len = getline (&line, &linesz, f)) > 0)
155    {
156      if (line[len - 1] == '\n')
157	line[len - 1] = '\0';
158
159      Dwarf_Addr start, end, offset;
160      unsigned int dmajor, dminor;
161      uint64_t ino;
162      int nread = -1;
163      if (sscanf (line, "%" PRIx64 "-%" PRIx64 " %*s %" PRIx64
164		  " %x:%x %" PRIi64 " %n",
165		  &start, &end, &offset, &dmajor, &dminor, &ino, &nread) < 6
166	  || nread <= 0)
167	{
168	  free (line);
169	  return ENOEXEC;
170	}
171
172      /* If this is the special mapping AT_SYSINFO_EHDR pointed us at,
173	 report the last one and then this special one.  */
174      if (start == sysinfo_ehdr && start != 0)
175	{
176	  if (report ())
177	    {
178	    bad_report:
179	      free (line);
180	      fclose (f);
181	      return -1;
182	    }
183
184	  low = start;
185	  high = end;
186	  if (asprintf (&last_file, "[vdso: %d]", (int) pid) < 0
187	      || report ())
188	    goto bad_report;
189	}
190
191      char *file = line + nread + strspn (line + nread, " \t");
192      if (file[0] == '\0' || (ino == 0 && dmajor == 0 && dminor == 0))
193	/* This line doesn't indicate a file mapping.  */
194	continue;
195
196      if (last_file != NULL
197	  && ino == last_ino && dmajor == last_dmajor && dminor == last_dminor)
198	{
199	  /* This is another portion of the same file's mapping.  */
200	  assert (!strcmp (last_file, file));
201	  high = end;
202	}
203      else
204	{
205	  /* This is a different file mapping.  Report the last one.  */
206	  if (report ())
207	    goto bad_report;
208	  low = start;
209	  high = end;
210	  last_file = strdup (file);
211	  last_ino = ino;
212	  last_dmajor = dmajor;
213	  last_dminor = dminor;
214	}
215    }
216  free (line);
217
218  int result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC;
219
220  /* Report the final one.  */
221  bool lose = report ();
222
223  return result != 0 ? result : lose ? -1 : 0;
224}
225
226int
227dwfl_linux_proc_maps_report (Dwfl *dwfl, FILE *f)
228{
229  return proc_maps_report (dwfl, f, 0, 0);
230}
231INTDEF (dwfl_linux_proc_maps_report)
232
233int
234dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid)
235{
236  if (dwfl == NULL)
237    return -1;
238
239  /* We'll notice the AT_SYSINFO_EHDR address specially when we hit it.  */
240  GElf_Addr sysinfo_ehdr = 0;
241  int result = find_sysinfo_ehdr (pid, &sysinfo_ehdr);
242  if (result != 0)
243    return result;
244
245  char *fname;
246  if (asprintf (&fname, PROCMAPSFMT, pid) < 0)
247    return ENOMEM;
248
249  FILE *f = fopen (fname, "r");
250  free (fname);
251  if (f == NULL)
252    return errno;
253
254  (void) __fsetlocking (f, FSETLOCKING_BYCALLER);
255
256  result = proc_maps_report (dwfl, f, sysinfo_ehdr, pid);
257
258  fclose (f);
259
260  return result;
261}
262INTDEF (dwfl_linux_proc_report)
263
264static ssize_t
265read_proc_memory (void *arg, void *data, GElf_Addr address,
266		  size_t minread, size_t maxread)
267{
268  const int fd = *(const int *) arg;
269  ssize_t nread = pread64 (fd, data, maxread, (off64_t) address);
270  /* Some kernels don't actually let us do this read, ignore those errors.  */
271  if (nread < 0 && (errno == EINVAL || errno == EPERM))
272    return 0;
273  if (nread > 0 && (size_t) nread < minread)
274    nread = 0;
275  return nread;
276}
277
278extern Elf *elf_from_remote_memory (GElf_Addr ehdr_vma,
279				    GElf_Addr *loadbasep,
280				    ssize_t (*read_memory) (void *arg,
281							    void *data,
282							    GElf_Addr address,
283							    size_t minread,
284							    size_t maxread),
285				    void *arg);
286
287
288/* Dwfl_Callbacks.find_elf */
289
290int
291dwfl_linux_proc_find_elf (Dwfl_Module *mod __attribute__ ((unused)),
292			  void **userdata __attribute__ ((unused)),
293			  const char *module_name, Dwarf_Addr base,
294			  char **file_name, Elf **elfp)
295{
296  if (module_name[0] == '/')
297    {
298      int fd = open64 (module_name, O_RDONLY);
299      if (fd >= 0)
300	{
301	  *file_name = strdup (module_name);
302	  if (*file_name == NULL)
303	    {
304	      close (fd);
305	      return ENOMEM;
306	    }
307	}
308      return fd;
309    }
310
311  int pid;
312  if (sscanf (module_name, "[vdso: %d]", &pid) == 1)
313    {
314      /* Special case for in-memory ELF image.  */
315
316      char *fname;
317      if (asprintf (&fname, PROCMEMFMT, pid) < 0)
318	return -1;
319
320      int fd = open64 (fname, O_RDONLY);
321      free (fname);
322      if (fd < 0)
323	return -1;
324
325      *elfp = elf_from_remote_memory (base, NULL, &read_proc_memory, &fd);
326
327      close (fd);
328
329      *file_name = NULL;
330      return -1;
331    }
332
333  abort ();
334  return -1;
335}
336INTDEF (dwfl_linux_proc_find_elf)
337