aboutsummaryrefslogtreecommitdiff
path: root/gdb/nat/linux-maps.c
blob: ef3da6aa2d282dd49ec097cde3a4db0a514228a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
/* Linux-specific memory maps manipulation routines.
   Copyright (C) 2015 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program 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 a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "common-defs.h"
#include "linux-maps.h"
#include <ctype.h>
#include "target/target-utils.h"
#include "gdb_regex.h"
#include "target/target.h"

/* This struct is used to map flags found in the "VmFlags:" field (in
   the /proc/<PID>/smaps file).  */

struct smaps_vmflags
  {
    /* Zero if this structure has not been initialized yet.  It
       probably means that the Linux kernel being used does not emit
       the "VmFlags:" field on "/proc/PID/smaps".  */

    unsigned int initialized_p : 1;

    /* Memory mapped I/O area (VM_IO, "io").  */

    unsigned int io_page : 1;

    /* Area uses huge TLB pages (VM_HUGETLB, "ht").  */

    unsigned int uses_huge_tlb : 1;

    /* Do not include this memory region on the coredump (VM_DONTDUMP, "dd").  */

    unsigned int exclude_coredump : 1;

    /* Is this a MAP_SHARED mapping (VM_SHARED, "sh").  */

    unsigned int shared_mapping : 1;
  };

/* Service function for corefiles and info proc.  */

void
read_mapping (const char *line,
	      ULONGEST *addr, ULONGEST *endaddr,
	      const char **permissions, size_t *permissions_len,
	      ULONGEST *offset,
              const char **device, size_t *device_len,
	      ULONGEST *inode,
	      const char **filename)
{
  const char *p = line;

  *addr = strtoulst (p, &p, 16);
  if (*p == '-')
    p++;
  *endaddr = strtoulst (p, &p, 16);

  p = skip_spaces_const (p);
  *permissions = p;
  while (*p && !isspace (*p))
    p++;
  *permissions_len = p - *permissions;

  *offset = strtoulst (p, &p, 16);

  p = skip_spaces_const (p);
  *device = p;
  while (*p && !isspace (*p))
    p++;
  *device_len = p - *device;

  *inode = strtoulst (p, &p, 10);

  p = skip_spaces_const (p);
  *filename = p;
}

/* Helper function to decode the "VmFlags" field in /proc/PID/smaps.

   This function was based on the documentation found on
   <Documentation/filesystems/proc.txt>, on the Linux kernel.

   Linux kernels before commit
   834f82e2aa9a8ede94b17b656329f850c1471514 (3.10) do not have this
   field on smaps.  */

static void
decode_vmflags (char *p, struct smaps_vmflags *v)
{
  char *saveptr = NULL;
  const char *s;

  v->initialized_p = 1;
  p = skip_to_space (p);
  p = skip_spaces (p);

  for (s = strtok_r (p, " ", &saveptr);
       s != NULL;
       s = strtok_r (NULL, " ", &saveptr))
    {
      if (strcmp (s, "io") == 0)
	v->io_page = 1;
      else if (strcmp (s, "ht") == 0)
	v->uses_huge_tlb = 1;
      else if (strcmp (s, "dd") == 0)
	v->exclude_coredump = 1;
      else if (strcmp (s, "sh") == 0)
	v->shared_mapping = 1;
    }
}

/* Return 1 if the memory mapping is anonymous, 0 otherwise.

   FILENAME is the name of the file present in the first line of the
   memory mapping, in the "/proc/PID/smaps" output.  For example, if
   the first line is:

   7fd0ca877000-7fd0d0da0000 r--p 00000000 fd:02 2100770   /path/to/file

   Then FILENAME will be "/path/to/file".  */

static int
mapping_is_anonymous_p (const char *filename)
{
  static regex_t dev_zero_regex, shmem_file_regex, file_deleted_regex;
  static int init_regex_p = 0;

  if (!init_regex_p)
    {
      struct cleanup *c = make_cleanup (null_cleanup, NULL);

      /* Let's be pessimistic and assume there will be an error while
	 compiling the regex'es.  */
      init_regex_p = -1;

      /* DEV_ZERO_REGEX matches "/dev/zero" filenames (with or
	 without the "(deleted)" string in the end).  We know for
	 sure, based on the Linux kernel code, that memory mappings
	 whose associated filename is "/dev/zero" are guaranteed to be
	 MAP_ANONYMOUS.  */
      compile_rx_or_error (&dev_zero_regex, "^/dev/zero\\( (deleted)\\)\\?$",
			   _("Could not compile regex to match /dev/zero "
			     "filename"));
      /* SHMEM_FILE_REGEX matches "/SYSV%08x" filenames (with or
	 without the "(deleted)" string in the end).  These filenames
	 refer to shared memory (shmem), and memory mappings
	 associated with them are MAP_ANONYMOUS as well.  */
      compile_rx_or_error (&shmem_file_regex,
			   "^/\\?SYSV[0-9a-fA-F]\\{8\\}\\( (deleted)\\)\\?$",
			   _("Could not compile regex to match shmem "
			     "filenames"));
      /* FILE_DELETED_REGEX is a heuristic we use to try to mimic the
	 Linux kernel's 'n_link == 0' code, which is responsible to
	 decide if it is dealing with a 'MAP_SHARED | MAP_ANONYMOUS'
	 mapping.  In other words, if FILE_DELETED_REGEX matches, it
	 does not necessarily mean that we are dealing with an
	 anonymous shared mapping.  However, there is no easy way to
	 detect this currently, so this is the best approximation we
	 have.

	 As a result, GDB will dump readonly pages of deleted
	 executables when using the default value of coredump_filter
	 (0x33), while the Linux kernel will not dump those pages.
	 But we can live with that.  */
      compile_rx_or_error (&file_deleted_regex, " (deleted)$",
			   _("Could not compile regex to match "
			     "'<file> (deleted)'"));
      /* We will never release these regexes, so just discard the
	 cleanups.  */
      discard_cleanups (c);

      /* If we reached this point, then everything succeeded.  */
      init_regex_p = 1;
    }

  if (init_regex_p == -1)
    {
      const char deleted[] = " (deleted)";
      size_t del_len = sizeof (deleted) - 1;
      size_t filename_len = strlen (filename);

      /* There was an error while compiling the regex'es above.  In
	 order to try to give some reliable information to the caller,
	 we just try to find the string " (deleted)" in the filename.
	 If we managed to find it, then we assume the mapping is
	 anonymous.  */
      return (filename_len >= del_len
	      && strcmp (filename + filename_len - del_len, deleted) == 0);
    }

  if (*filename == '\0'
      || regexec (&dev_zero_regex, filename, 0, NULL, 0) == 0
      || regexec (&shmem_file_regex, filename, 0, NULL, 0) == 0
      || regexec (&file_deleted_regex, filename, 0, NULL, 0) == 0)
    return 1;

  return 0;
}

/* Return 0 if the memory mapping (which is related to FILTERFLAGS, V,
   MAYBE_PRIVATE_P, and MAPPING_ANONYMOUS_P) should not be dumped, or
   greater than 0 if it should.

   In a nutshell, this is the logic that we follow in order to decide
   if a mapping should be dumped or not.

   - If the mapping is associated to a file whose name ends with
     " (deleted)", or if the file is "/dev/zero", or if it is
     "/SYSV%08x" (shared memory), or if there is no file associated
     with it, or if the AnonHugePages: or the Anonymous: fields in the
     /proc/PID/smaps have contents, then GDB considers this mapping to
     be anonymous.  Otherwise, GDB considers this mapping to be a
     file-backed mapping (because there will be a file associated with
     it).
 
     It is worth mentioning that, from all those checks described
     above, the most fragile is the one to see if the file name ends
     with " (deleted)".  This does not necessarily mean that the
     mapping is anonymous, because the deleted file associated with
     the mapping may have been a hard link to another file, for
     example.  The Linux kernel checks to see if "i_nlink == 0", but
     GDB cannot easily (and normally) do this check (iff running as
     root, it could find the mapping in /proc/PID/map_files/ and
     determine whether there still are other hard links to the
     inode/file).  Therefore, we made a compromise here, and we assume
     that if the file name ends with " (deleted)", then the mapping is
     indeed anonymous.  FWIW, this is something the Linux kernel could
     do better: expose this information in a more direct way.
 
   - If we see the flag "sh" in the "VmFlags:" field (in
     /proc/PID/smaps), then certainly the memory mapping is shared
     (VM_SHARED).  If we have access to the VmFlags, and we don't see
     the "sh" there, then certainly the mapping is private.  However,
     Linux kernels before commit
     834f82e2aa9a8ede94b17b656329f850c1471514 (3.10) do not have the
     "VmFlags:" field; in that case, we use another heuristic: if we
     see 'p' in the permission flags, then we assume that the mapping
     is private, even though the presence of the 's' flag there would
     mean VM_MAYSHARE, which means the mapping could still be private.
     This should work OK enough, however.  */

static int
dump_mapping_p (enum filterflags filterflags, const struct smaps_vmflags *v,
		int maybe_private_p, int mapping_anon_p, int mapping_file_p,
		const char *filename)
{
  /* Initially, we trust in what we received from our caller.  This
     value may not be very precise (i.e., it was probably gathered
     from the permission line in the /proc/PID/smaps list, which
     actually refers to VM_MAYSHARE, and not VM_SHARED), but it is
     what we have until we take a look at the "VmFlags:" field
     (assuming that the version of the Linux kernel being used
     supports it, of course).  */
  int private_p = maybe_private_p;

  /* We always dump vDSO and vsyscall mappings, because it's likely that
     there'll be no file to read the contents from at core load time.
     The kernel does the same.  */
  if (strcmp ("[vdso]", filename) == 0
      || strcmp ("[vsyscall]", filename) == 0)
    return 1;

  if (v->initialized_p)
    {
      /* We never dump I/O mappings.  */
      if (v->io_page)
	return 0;

      /* Check if we should exclude this mapping.  */
      if (v->exclude_coredump)
	return 0;

      /* Update our notion of whether this mapping is shared or
	 private based on a trustworthy value.  */
      private_p = !v->shared_mapping;

      /* HugeTLB checking.  */
      if (v->uses_huge_tlb)
	{
	  if ((private_p && (filterflags & COREFILTER_HUGETLB_PRIVATE))
	      || (!private_p && (filterflags & COREFILTER_HUGETLB_SHARED)))
	    return 1;

	  return 0;
	}
    }

  if (private_p)
    {
      if (mapping_anon_p && mapping_file_p)
	{
	  /* This is a special situation.  It can happen when we see a
	     mapping that is file-backed, but that contains anonymous
	     pages.  */
	  return ((filterflags & COREFILTER_ANON_PRIVATE) != 0
		  || (filterflags & COREFILTER_MAPPED_PRIVATE) != 0);
	}
      else if (mapping_anon_p)
	return (filterflags & COREFILTER_ANON_PRIVATE) != 0;
      else
	return (filterflags & COREFILTER_MAPPED_PRIVATE) != 0;
    }
  else
    {
      if (mapping_anon_p && mapping_file_p)
	{
	  /* This is a special situation.  It can happen when we see a
	     mapping that is file-backed, but that contains anonymous
	     pages.  */
	  return ((filterflags & COREFILTER_ANON_SHARED) != 0
		  || (filterflags & COREFILTER_MAPPED_SHARED) != 0);
	}
      else if (mapping_anon_p)
	return (filterflags & COREFILTER_ANON_SHARED) != 0;
      else
	return (filterflags & COREFILTER_MAPPED_SHARED) != 0;
    }
}

/* List memory regions in the inferior PID matched to FILTERFLAGS for
   a corefile.  Call FUNC with FUNC_DATA for each such region.  Return
   immediately with the value returned by FUNC if it is non-zero.
   *MEMORY_TO_FREE_PTR should be registered to be freed automatically if
   called FUNC throws an exception.  MEMORY_TO_FREE_PTR can be also
   passed as NULL if it is not used.  Return -1 if error occurs, 0 if
   all memory regions have been processed or return the value from FUNC
   if FUNC returns non-zero.  */

int
linux_find_memory_regions_full (pid_t pid, enum filterflags filterflags,
				linux_find_memory_region_ftype *func,
				void *func_data)
{
  char mapsfilename[100];
  char *data;

  xsnprintf (mapsfilename, sizeof mapsfilename, "/proc/%d/smaps", pid);
  data = target_fileio_read_stralloc (NULL, mapsfilename);
  if (data == NULL)
    {
      /* Older Linux kernels did not support /proc/PID/smaps.  */
      xsnprintf (mapsfilename, sizeof mapsfilename, "/proc/%d/maps", pid);
      data = target_fileio_read_stralloc (NULL, mapsfilename);
    }

  if (data != NULL)
    {
      struct cleanup *cleanup = make_cleanup (xfree, data);
      char *line, *t;
      int retval = 0;

      line = strtok_r (data, "\n", &t);
      while (line != NULL)
	{
	  ULONGEST addr, endaddr, offset, inode;
	  const char *permissions, *device, *filename;
	  struct smaps_vmflags v;
	  size_t permissions_len, device_len;
	  int read, write, exec, priv;
	  int has_anonymous = 0;
	  int should_dump_p = 0;
	  int mapping_anon_p;
	  int mapping_file_p;

	  memset (&v, 0, sizeof (v));
	  read_mapping (line, &addr, &endaddr, &permissions, &permissions_len,
			&offset, &device, &device_len, &inode, &filename);
	  mapping_anon_p = mapping_is_anonymous_p (filename);
	  /* If the mapping is not anonymous, then we can consider it
	     to be file-backed.  These two states (anonymous or
	     file-backed) seem to be exclusive, but they can actually
	     coexist.  For example, if a file-backed mapping has
	     "Anonymous:" pages (see more below), then the Linux
	     kernel will dump this mapping when the user specified
	     that she only wants anonymous mappings in the corefile
	     (*even* when she explicitly disabled the dumping of
	     file-backed mappings).  */
	  mapping_file_p = !mapping_anon_p;

	  /* Decode permissions.  */
	  read = (memchr (permissions, 'r', permissions_len) != 0);
	  write = (memchr (permissions, 'w', permissions_len) != 0);
	  exec = (memchr (permissions, 'x', permissions_len) != 0);
	  /* 'private' here actually means VM_MAYSHARE, and not
	     VM_SHARED.  In order to know if a mapping is really
	     private or not, we must check the flag "sh" in the
	     VmFlags field.  This is done by decode_vmflags.  However,
	     if we are using a Linux kernel released before the commit
	     834f82e2aa9a8ede94b17b656329f850c1471514 (3.10), we will
	     not have the VmFlags there.  In this case, there is
	     really no way to know if we are dealing with VM_SHARED,
	     so we just assume that VM_MAYSHARE is enough.  */
	  priv = memchr (permissions, 'p', permissions_len) != 0;

	  /* Try to detect if region should be dumped by parsing smaps
	     counters.  */
	  for (line = strtok_r (NULL, "\n", &t);
	       line != NULL && line[0] >= 'A' && line[0] <= 'Z';
	       line = strtok_r (NULL, "\n", &t))
	    {
	      char keyword[64 + 1];

	      if (sscanf (line, "%64s", keyword) != 1)
		{
		  warning (_("Error parsing {s,}maps file '%s'"), mapsfilename);
		  break;
		}

	      if (strcmp (keyword, "Anonymous:") == 0)
		{
		  /* Older Linux kernels did not support the
		     "Anonymous:" counter.  Check it here.  */
		  has_anonymous = 1;
		}
	      else if (strcmp (keyword, "VmFlags:") == 0)
		decode_vmflags (line, &v);

	      if (strcmp (keyword, "AnonHugePages:") == 0
		  || strcmp (keyword, "Anonymous:") == 0)
		{
		  unsigned long number;

		  if (sscanf (line, "%*s%lu", &number) != 1)
		    {
		      warning (_("Error parsing {s,}maps file '%s' number"),
			       mapsfilename);
		      break;
		    }
		  if (number > 0)
		    {
		      /* Even if we are dealing with a file-backed
			 mapping, if it contains anonymous pages we
			 consider it to be *also* an anonymous
			 mapping, because this is what the Linux
			 kernel does:

			 // Dump segments that have been written to.
			 if (vma->anon_vma && FILTER(ANON_PRIVATE))
			 	goto whole;

			 Note that if the mapping is already marked as
			 file-backed (i.e., mapping_file_p is
			 non-zero), then this is a special case, and
			 this mapping will be dumped either when the
			 user wants to dump file-backed *or* anonymous
			 mappings.  */
		      mapping_anon_p = 1;
		    }
		}
	    }

	  if (has_anonymous)
	    should_dump_p = dump_mapping_p (filterflags, &v, priv,
					    mapping_anon_p, mapping_file_p,
					    filename);
	  else
	    {
	      /* Older Linux kernels did not support the "Anonymous:" counter.
		 If it is missing, we can't be sure - dump all the pages.  */
	      should_dump_p = 1;
	    }

	  /* Invoke the callback function to create the corefile segment.  */
	  if (should_dump_p)
	    retval = func (addr, endaddr - addr, offset, inode,
			   read, write, exec,
			   1, /* MODIFIED is true because we want to dump the
				 mapping.  */
			   filename, func_data);
	  if (retval != 0)
	    break;
	}

      do_cleanups (cleanup);
      return retval;
    }

  return -1;
}