1//===-- sanitizer_procmaps_common.cc --------------------------------------===//
2//
3// This file is distributed under the University of Illinois Open Source
4// License. See LICENSE.TXT for details.
5//
6//===----------------------------------------------------------------------===//
7//
8// Information about the process mappings (common parts).
9//===----------------------------------------------------------------------===//
10
11#include "sanitizer_platform.h"
12#if SANITIZER_FREEBSD || SANITIZER_LINUX
13#include "sanitizer_common.h"
14#include "sanitizer_placement_new.h"
15#include "sanitizer_procmaps.h"
16
17namespace __sanitizer {
18
19// Linker initialized.
20ProcSelfMapsBuff MemoryMappingLayout::cached_proc_self_maps_;
21StaticSpinMutex MemoryMappingLayout::cache_lock_;  // Linker initialized.
22
23static int TranslateDigit(char c) {
24  if (c >= '0' && c <= '9')
25    return c - '0';
26  if (c >= 'a' && c <= 'f')
27    return c - 'a' + 10;
28  if (c >= 'A' && c <= 'F')
29    return c - 'A' + 10;
30  return -1;
31}
32
33// Parse a number and promote 'p' up to the first non-digit character.
34static uptr ParseNumber(const char **p, int base) {
35  uptr n = 0;
36  int d;
37  CHECK(base >= 2 && base <= 16);
38  while ((d = TranslateDigit(**p)) >= 0 && d < base) {
39    n = n * base + d;
40    (*p)++;
41  }
42  return n;
43}
44
45bool IsDecimal(char c) {
46  int d = TranslateDigit(c);
47  return d >= 0 && d < 10;
48}
49
50uptr ParseDecimal(const char **p) {
51  return ParseNumber(p, 10);
52}
53
54bool IsHex(char c) {
55  int d = TranslateDigit(c);
56  return d >= 0 && d < 16;
57}
58
59uptr ParseHex(const char **p) {
60  return ParseNumber(p, 16);
61}
62
63MemoryMappingLayout::MemoryMappingLayout(bool cache_enabled) {
64  ReadProcMaps(&proc_self_maps_);
65  if (cache_enabled) {
66    if (proc_self_maps_.mmaped_size == 0) {
67      LoadFromCache();
68      CHECK_GT(proc_self_maps_.len, 0);
69    }
70  } else {
71    CHECK_GT(proc_self_maps_.mmaped_size, 0);
72  }
73  Reset();
74  // FIXME: in the future we may want to cache the mappings on demand only.
75  if (cache_enabled)
76    CacheMemoryMappings();
77}
78
79MemoryMappingLayout::~MemoryMappingLayout() {
80  // Only unmap the buffer if it is different from the cached one. Otherwise
81  // it will be unmapped when the cache is refreshed.
82  if (proc_self_maps_.data != cached_proc_self_maps_.data) {
83    UnmapOrDie(proc_self_maps_.data, proc_self_maps_.mmaped_size);
84  }
85}
86
87void MemoryMappingLayout::Reset() {
88  current_ = proc_self_maps_.data;
89}
90
91// static
92void MemoryMappingLayout::CacheMemoryMappings() {
93  SpinMutexLock l(&cache_lock_);
94  // Don't invalidate the cache if the mappings are unavailable.
95  ProcSelfMapsBuff old_proc_self_maps;
96  old_proc_self_maps = cached_proc_self_maps_;
97  ReadProcMaps(&cached_proc_self_maps_);
98  if (cached_proc_self_maps_.mmaped_size == 0) {
99    cached_proc_self_maps_ = old_proc_self_maps;
100  } else {
101    if (old_proc_self_maps.mmaped_size) {
102      UnmapOrDie(old_proc_self_maps.data,
103                 old_proc_self_maps.mmaped_size);
104    }
105  }
106}
107
108void MemoryMappingLayout::LoadFromCache() {
109  SpinMutexLock l(&cache_lock_);
110  if (cached_proc_self_maps_.data) {
111    proc_self_maps_ = cached_proc_self_maps_;
112  }
113}
114
115uptr MemoryMappingLayout::DumpListOfModules(LoadedModule *modules,
116                                            uptr max_modules,
117                                            string_predicate_t filter) {
118  Reset();
119  uptr cur_beg, cur_end, cur_offset, prot;
120  InternalScopedBuffer<char> module_name(kMaxPathLength);
121  uptr n_modules = 0;
122  for (uptr i = 0; n_modules < max_modules &&
123                       Next(&cur_beg, &cur_end, &cur_offset, module_name.data(),
124                            module_name.size(), &prot);
125       i++) {
126    const char *cur_name = module_name.data();
127    if (cur_name[0] == '\0')
128      continue;
129    if (filter && !filter(cur_name))
130      continue;
131    void *mem = &modules[n_modules];
132    // Don't subtract 'cur_beg' from the first entry:
133    // * If a binary is compiled w/o -pie, then the first entry in
134    //   process maps is likely the binary itself (all dynamic libs
135    //   are mapped higher in address space). For such a binary,
136    //   instruction offset in binary coincides with the actual
137    //   instruction address in virtual memory (as code section
138    //   is mapped to a fixed memory range).
139    // * If a binary is compiled with -pie, all the modules are
140    //   mapped high at address space (in particular, higher than
141    //   shadow memory of the tool), so the module can't be the
142    //   first entry.
143    uptr base_address = (i ? cur_beg : 0) - cur_offset;
144    LoadedModule *cur_module = new(mem) LoadedModule(cur_name, base_address);
145    cur_module->addAddressRange(cur_beg, cur_end, prot & kProtectionExecute);
146    n_modules++;
147  }
148  return n_modules;
149}
150
151void GetMemoryProfile(fill_profile_f cb, uptr *stats, uptr stats_size) {
152  char *smaps = 0;
153  uptr smaps_cap = 0;
154  uptr smaps_len = ReadFileToBuffer("/proc/self/smaps",
155      &smaps, &smaps_cap, 64<<20);
156  uptr start = 0;
157  bool file = false;
158  const char *pos = smaps;
159  while (pos < smaps + smaps_len) {
160    if (IsHex(pos[0])) {
161      start = ParseHex(&pos);
162      for (; *pos != '/' && *pos > '\n'; pos++) {}
163      file = *pos == '/';
164    } else if (internal_strncmp(pos, "Rss:", 4) == 0) {
165      while (!IsDecimal(*pos)) pos++;
166      uptr rss = ParseDecimal(&pos) * 1024;
167      cb(start, rss, file, stats, stats_size);
168    }
169    while (*pos++ != '\n') {}
170  }
171  UnmapOrDie(smaps, smaps_cap);
172}
173
174}  // namespace __sanitizer
175
176#endif  // SANITIZER_FREEBSD || SANITIZER_LINUX
177