1 | //===-- sanitizer_procmaps_common.cpp -------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // Information about the process mappings (common parts). |
10 | //===----------------------------------------------------------------------===// |
11 | |
12 | #include "sanitizer_platform.h" |
13 | |
14 | #if SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_NETBSD || \ |
15 | SANITIZER_SOLARIS |
16 | |
17 | #include "sanitizer_common.h" |
18 | #include "sanitizer_placement_new.h" |
19 | #include "sanitizer_procmaps.h" |
20 | |
21 | namespace __sanitizer { |
22 | |
23 | static ProcSelfMapsBuff cached_proc_self_maps; |
24 | static StaticSpinMutex cache_lock; |
25 | |
26 | static int TranslateDigit(char c) { |
27 | if (c >= '0' && c <= '9') |
28 | return c - '0'; |
29 | if (c >= 'a' && c <= 'f') |
30 | return c - 'a' + 10; |
31 | if (c >= 'A' && c <= 'F') |
32 | return c - 'A' + 10; |
33 | return -1; |
34 | } |
35 | |
36 | // Parse a number and promote 'p' up to the first non-digit character. |
37 | static uptr ParseNumber(const char **p, int base) { |
38 | uptr n = 0; |
39 | int d; |
40 | CHECK(base >= 2 && base <= 16); |
41 | while ((d = TranslateDigit(c: **p)) >= 0 && d < base) { |
42 | n = n * base + d; |
43 | (*p)++; |
44 | } |
45 | return n; |
46 | } |
47 | |
48 | bool IsDecimal(char c) { |
49 | int d = TranslateDigit(c); |
50 | return d >= 0 && d < 10; |
51 | } |
52 | |
53 | uptr ParseDecimal(const char **p) { |
54 | return ParseNumber(p, base: 10); |
55 | } |
56 | |
57 | bool IsHex(char c) { |
58 | int d = TranslateDigit(c); |
59 | return d >= 0 && d < 16; |
60 | } |
61 | |
62 | uptr ParseHex(const char **p) { |
63 | return ParseNumber(p, base: 16); |
64 | } |
65 | |
66 | void MemoryMappedSegment::AddAddressRanges(LoadedModule *module) { |
67 | // data_ should be unused on this platform |
68 | CHECK(!data_); |
69 | module->addAddressRange(beg: start, end, executable: IsExecutable(), writable: IsWritable()); |
70 | } |
71 | |
72 | MemoryMappingLayout::MemoryMappingLayout(bool cache_enabled) { |
73 | // FIXME: in the future we may want to cache the mappings on demand only. |
74 | if (cache_enabled) |
75 | CacheMemoryMappings(); |
76 | |
77 | // Read maps after the cache update to capture the maps/unmaps happening in |
78 | // the process of updating. |
79 | ReadProcMaps(proc_maps: &data_.proc_self_maps); |
80 | if (cache_enabled && data_.proc_self_maps.mmaped_size == 0) |
81 | LoadFromCache(); |
82 | |
83 | Reset(); |
84 | } |
85 | |
86 | bool MemoryMappingLayout::Error() const { |
87 | return data_.current == nullptr; |
88 | } |
89 | |
90 | MemoryMappingLayout::~MemoryMappingLayout() { |
91 | // Only unmap the buffer if it is different from the cached one. Otherwise |
92 | // it will be unmapped when the cache is refreshed. |
93 | if (data_.proc_self_maps.data != cached_proc_self_maps.data) |
94 | UnmapOrDie(addr: data_.proc_self_maps.data, size: data_.proc_self_maps.mmaped_size); |
95 | } |
96 | |
97 | void MemoryMappingLayout::Reset() { |
98 | data_.current = data_.proc_self_maps.data; |
99 | } |
100 | |
101 | // static |
102 | void MemoryMappingLayout::CacheMemoryMappings() { |
103 | ProcSelfMapsBuff new_proc_self_maps; |
104 | ReadProcMaps(proc_maps: &new_proc_self_maps); |
105 | // Don't invalidate the cache if the mappings are unavailable. |
106 | if (new_proc_self_maps.mmaped_size == 0) |
107 | return; |
108 | SpinMutexLock l(&cache_lock); |
109 | if (cached_proc_self_maps.mmaped_size) |
110 | UnmapOrDie(addr: cached_proc_self_maps.data, size: cached_proc_self_maps.mmaped_size); |
111 | cached_proc_self_maps = new_proc_self_maps; |
112 | } |
113 | |
114 | void MemoryMappingLayout::LoadFromCache() { |
115 | SpinMutexLock l(&cache_lock); |
116 | if (cached_proc_self_maps.data) |
117 | data_.proc_self_maps = cached_proc_self_maps; |
118 | } |
119 | |
120 | void MemoryMappingLayout::DumpListOfModules( |
121 | InternalMmapVectorNoCtor<LoadedModule> *modules) { |
122 | Reset(); |
123 | InternalMmapVector<char> module_name(kMaxPathLength); |
124 | MemoryMappedSegment segment(module_name.data(), module_name.size()); |
125 | for (uptr i = 0; Next(segment: &segment); i++) { |
126 | const char *cur_name = segment.filename; |
127 | if (cur_name[0] == '\0') |
128 | continue; |
129 | // Don't subtract 'cur_beg' from the first entry: |
130 | // * If a binary is compiled w/o -pie, then the first entry in |
131 | // process maps is likely the binary itself (all dynamic libs |
132 | // are mapped higher in address space). For such a binary, |
133 | // instruction offset in binary coincides with the actual |
134 | // instruction address in virtual memory (as code section |
135 | // is mapped to a fixed memory range). |
136 | // * If a binary is compiled with -pie, all the modules are |
137 | // mapped high at address space (in particular, higher than |
138 | // shadow memory of the tool), so the module can't be the |
139 | // first entry. |
140 | uptr base_address = (i ? segment.start : 0) - segment.offset; |
141 | LoadedModule cur_module; |
142 | cur_module.set(module_name: cur_name, base_address); |
143 | segment.AddAddressRanges(module: &cur_module); |
144 | modules->push_back(element: cur_module); |
145 | } |
146 | } |
147 | |
148 | #if SANITIZER_LINUX || SANITIZER_ANDROID || SANITIZER_SOLARIS |
149 | void GetMemoryProfile(fill_profile_f cb, uptr *stats) { |
150 | char *smaps = nullptr; |
151 | uptr smaps_cap = 0; |
152 | uptr smaps_len = 0; |
153 | if (!ReadFileToBuffer(file_name: "/proc/self/smaps" , buff: &smaps, buff_size: &smaps_cap, read_len: &smaps_len)) |
154 | return; |
155 | ParseUnixMemoryProfile(cb, stats, smaps, smaps_len); |
156 | UnmapOrDie(addr: smaps, size: smaps_cap); |
157 | } |
158 | |
159 | void ParseUnixMemoryProfile(fill_profile_f cb, uptr *stats, char *smaps, |
160 | uptr smaps_len) { |
161 | uptr start = 0; |
162 | bool file = false; |
163 | const char *pos = smaps; |
164 | char *end = smaps + smaps_len; |
165 | if (smaps_len < 2) |
166 | return; |
167 | // The following parsing can crash on almost every line |
168 | // in the case of malformed/truncated input. |
169 | // Fixing that is hard b/c e.g. ParseDecimal does not |
170 | // even accept end of the buffer and assumes well-formed input. |
171 | // So instead we patch end of the input a bit, |
172 | // it does not affect well-formed complete inputs. |
173 | *--end = 0; |
174 | *--end = '\n'; |
175 | while (pos < end) { |
176 | if (IsHex(c: pos[0])) { |
177 | start = ParseHex(p: &pos); |
178 | for (; *pos != '/' && *pos > '\n'; pos++) {} |
179 | file = *pos == '/'; |
180 | } else if (internal_strncmp(s1: pos, s2: "Rss:" , n: 4) == 0) { |
181 | while (pos < end && !IsDecimal(c: *pos)) pos++; |
182 | uptr = ParseDecimal(p: &pos) * 1024; |
183 | cb(start, rss, file, stats); |
184 | } |
185 | while (*pos++ != '\n') {} |
186 | } |
187 | } |
188 | #endif |
189 | |
190 | } // namespace __sanitizer |
191 | |
192 | #endif |
193 | |