1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #pragma once
18
19 #include <ctype.h>
20 #include <fcntl.h>
21 #include <inttypes.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/mman.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29
30 #include <functional>
31 #include <string>
32 #include <vector>
33
34 #include <android-base/file.h>
35 #include <android-base/strings.h>
36
37 namespace android {
38 namespace procinfo {
39
40 /*
41 * The populated fields of MapInfo corresponds to the following fields of an entry
42 * in /proc/<pid>/maps:
43 *
44 * <start> -<end> ... <pgoff> ... <inode> <name>
45 * 790b07dc6000-790b07dd9000 r--p 00000000 fe:09 21068208 /system/lib64/foo.so
46 * |
47 * |
48 * |___ p - private (!<shared>)
49 * s - <shared>
50 */
51 struct MapInfo {
52 uint64_t start;
53 uint64_t end;
54 // NOTE: It should not be assumed the virtual addresses in range [start,end] all
55 // correspond to valid offsets on the backing file.
56 // See: MappedFileSize().
57 uint16_t flags;
58 uint64_t pgoff;
59 ino_t inode;
60 std::string name;
61 bool shared;
62
63 // With MTE globals, segments are remapped as anonymous mappings. They're
64 // named specifically to preserve offsets and as much of the basename as
65 // possible. For example,
66 // "[anon:mt:/data/local/tmp/debuggerd_test/arm64/debuggerd_test64+108000]" is
67 // the name of anonymized mapping for debuggerd_test64 of the segment starting
68 // at 0x108000. The kernel only supports 80 characters (excluding the '[anon:'
69 // prefix and ']' suffix, but including the null terminator), and in those
70 // instances, we maintain the offset and as much of the basename as possible
71 // by left-truncation. For example:
72 // "[anon:mt:/data/nativetest64/bionic-unit-tests/bionic-loader-test-libs/libdlext_test.so+e000]"
73 // would become:
74 // "[anon:mt:...ivetest64/bionic-unit-tests/bionic-loader-test-libs/libdlext_test.so+e000]".
75 // For mappings under MTE globals, we thus post-process the name to extract the page offset, and
76 // canonicalize the name.
77 static constexpr const char* kMtePrefix = "[anon:mt:";
78 static constexpr size_t kMtePrefixLength = sizeof(kMtePrefix) - 1;
79
MaybeExtractMemtagGlobalsInfoMapInfo80 void MaybeExtractMemtagGlobalsInfo() {
81 if (!this->name.starts_with(kMtePrefix)) return;
82 if (this->name.back() != ']') return;
83
84 size_t offset_to_plus = this->name.rfind('+');
85 if (offset_to_plus == std::string::npos) return;
86 if (sscanf(this->name.c_str() + offset_to_plus + 1, "%" SCNx64 "]", &this->pgoff) != 1) return;
87
88 this->name =
89 std::string(this->name.begin() + kMtePrefixLength + 2, this->name.begin() + offset_to_plus);
90 }
91
MapInfoMapInfo92 MapInfo(uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, ino_t inode,
93 const char* name, bool shared)
94 : start(start),
95 end(end),
96 flags(flags),
97 pgoff(pgoff),
98 inode(inode),
99 name(name),
100 shared(shared) {
101 MaybeExtractMemtagGlobalsInfo();
102 }
103
MapInfoMapInfo104 MapInfo(const MapInfo& params)
105 : start(params.start),
106 end(params.end),
107 flags(params.flags),
108 pgoff(params.pgoff),
109 inode(params.inode),
110 name(params.name),
111 shared(params.shared) {}
112 };
113
114 typedef std::function<void(const MapInfo&)> MapInfoCallback;
115 typedef std::function<void(uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff,
116 ino_t inode, const char* name, bool shared)> MapInfoParamsCallback;
117
PassSpace(char ** p)118 static inline bool PassSpace(char** p) {
119 if (**p != ' ') {
120 return false;
121 }
122 while (**p == ' ') {
123 (*p)++;
124 }
125 return true;
126 }
127
PassXdigit(char ** p)128 static inline bool PassXdigit(char** p) {
129 if (!isxdigit(**p)) {
130 return false;
131 }
132 do {
133 (*p)++;
134 } while (isxdigit(**p));
135 return true;
136 }
137
138 // Parses the given line p pointing at proc/<pid>/maps content buffer and returns true on success
139 // and false on failure parsing. The first new line character of line will be replaced by the
140 // null character and *next_line will point to the character after the null.
141 //
142 // Example of how a parsed line look line:
143 // 00400000-00409000 r-xp 00000000 fc:00 426998 /usr/lib/gvfs/gvfsd-http
ParseMapsFileLine(char * p,uint64_t & start_addr,uint64_t & end_addr,uint16_t & flags,uint64_t & pgoff,ino_t & inode,char ** name,bool & shared,char ** next_line)144 static inline bool ParseMapsFileLine(char* p, uint64_t& start_addr, uint64_t& end_addr, uint16_t& flags,
145 uint64_t& pgoff, ino_t& inode, char** name, bool& shared, char** next_line) {
146 // Make the first new line character null.
147 *next_line = strchr(p, '\n');
148 if (*next_line != nullptr) {
149 **next_line = '\0';
150 (*next_line)++;
151 }
152
153 char* end;
154 // start_addr
155 start_addr = strtoull(p, &end, 16);
156 if (end == p || *end != '-') {
157 return false;
158 }
159 p = end + 1;
160 // end_addr
161 end_addr = strtoull(p, &end, 16);
162 if (end == p) {
163 return false;
164 }
165 p = end;
166 if (!PassSpace(&p)) {
167 return false;
168 }
169 // flags
170 flags = 0;
171 if (*p == 'r') {
172 flags |= PROT_READ;
173 } else if (*p != '-') {
174 return false;
175 }
176 p++;
177 if (*p == 'w') {
178 flags |= PROT_WRITE;
179 } else if (*p != '-') {
180 return false;
181 }
182 p++;
183 if (*p == 'x') {
184 flags |= PROT_EXEC;
185 } else if (*p != '-') {
186 return false;
187 }
188 p++;
189 if (*p != 'p' && *p != 's') {
190 return false;
191 }
192 shared = *p == 's';
193
194 p++;
195 if (!PassSpace(&p)) {
196 return false;
197 }
198 // pgoff
199 pgoff = strtoull(p, &end, 16);
200 if (end == p) {
201 return false;
202 }
203 p = end;
204 if (!PassSpace(&p)) {
205 return false;
206 }
207 // major:minor
208 if (!PassXdigit(&p) || *p++ != ':' || !PassXdigit(&p) || !PassSpace(&p)) {
209 return false;
210 }
211 // inode
212 inode = strtoull(p, &end, 10);
213 if (end == p) {
214 return false;
215 }
216 p = end;
217
218 if (*p != '\0' && !PassSpace(&p)) {
219 return false;
220 }
221
222 // Assumes that the first new character was replaced with null.
223 *name = p;
224
225 return true;
226 }
227
ReadMapFileContent(char * content,const MapInfoParamsCallback & callback)228 inline bool ReadMapFileContent(char* content, const MapInfoParamsCallback& callback) {
229 uint64_t start_addr;
230 uint64_t end_addr;
231 uint16_t flags;
232 uint64_t pgoff;
233 ino_t inode;
234 char* line_start = content;
235 char* next_line;
236 char* name;
237 bool shared;
238
239 while (line_start != nullptr && *line_start != '\0') {
240 bool parsed = ParseMapsFileLine(line_start, start_addr, end_addr, flags, pgoff,
241 inode, &name, shared, &next_line);
242 if (!parsed) {
243 return false;
244 }
245
246 line_start = next_line;
247 callback(start_addr, end_addr, flags, pgoff, inode, name, shared);
248 }
249 return true;
250 }
251
ReadMapFileContent(char * content,const MapInfoCallback & callback)252 inline bool ReadMapFileContent(char* content, const MapInfoCallback& callback) {
253 uint64_t start_addr;
254 uint64_t end_addr;
255 uint16_t flags;
256 uint64_t pgoff;
257 ino_t inode;
258 char* line_start = content;
259 char* next_line;
260 char* name;
261 bool shared;
262
263 while (line_start != nullptr && *line_start != '\0') {
264 bool parsed = ParseMapsFileLine(line_start, start_addr, end_addr, flags, pgoff,
265 inode, &name, shared, &next_line);
266 if (!parsed) {
267 return false;
268 }
269
270 line_start = next_line;
271 callback(MapInfo(start_addr, end_addr, flags, pgoff, inode, name, shared));
272 }
273 return true;
274 }
275
ReadMapFile(const std::string & map_file,const MapInfoCallback & callback)276 inline bool ReadMapFile(const std::string& map_file,
277 const MapInfoCallback& callback) {
278 std::string content;
279 if (!android::base::ReadFileToString(map_file, &content)) {
280 return false;
281 }
282 return ReadMapFileContent(&content[0], callback);
283 }
284
285
ReadMapFile(const std::string & map_file,const MapInfoParamsCallback & callback,std::string & mapsBuffer)286 inline bool ReadMapFile(const std::string& map_file, const MapInfoParamsCallback& callback,
287 std::string& mapsBuffer) {
288 if (!android::base::ReadFileToString(map_file, &mapsBuffer)) {
289 return false;
290 }
291 return ReadMapFileContent(&mapsBuffer[0], callback);
292 }
293
ReadMapFile(const std::string & map_file,const MapInfoParamsCallback & callback)294 inline bool ReadMapFile(const std::string& map_file,
295 const MapInfoParamsCallback& callback) {
296 std::string content;
297 return ReadMapFile(map_file, callback, content);
298 }
299
ReadProcessMaps(pid_t pid,const MapInfoCallback & callback)300 inline bool ReadProcessMaps(pid_t pid, const MapInfoCallback& callback) {
301 return ReadMapFile("/proc/" + std::to_string(pid) + "/maps", callback);
302 }
303
ReadProcessMaps(pid_t pid,const MapInfoParamsCallback & callback,std::string & mapsBuffer)304 inline bool ReadProcessMaps(pid_t pid, const MapInfoParamsCallback& callback,
305 std::string& mapsBuffer) {
306 return ReadMapFile("/proc/" + std::to_string(pid) + "/maps", callback, mapsBuffer);
307 }
308
ReadProcessMaps(pid_t pid,const MapInfoParamsCallback & callback)309 inline bool ReadProcessMaps(pid_t pid, const MapInfoParamsCallback& callback) {
310 std::string content;
311 return ReadProcessMaps(pid, callback, content);
312 }
313
ReadProcessMaps(pid_t pid,std::vector<MapInfo> * maps)314 inline bool ReadProcessMaps(pid_t pid, std::vector<MapInfo>* maps) {
315 return ReadProcessMaps(pid, [&](const MapInfo& mapinfo) { maps->emplace_back(mapinfo); });
316 }
317
318 // Reads maps file and executes given callback for each mapping
319 // Warning: buffer should not be modified asynchronously while this function executes
320 template <class CallbackType>
ReadMapFileAsyncSafe(const char * map_file,void * buffer,size_t buffer_size,const CallbackType & callback)321 inline bool ReadMapFileAsyncSafe(const char* map_file, void* buffer, size_t buffer_size,
322 const CallbackType& callback) {
323 if (buffer == nullptr || buffer_size == 0) {
324 return false;
325 }
326
327 int fd = open(map_file, O_RDONLY | O_CLOEXEC);
328 if (fd == -1) {
329 return false;
330 }
331
332 char* char_buffer = reinterpret_cast<char*>(buffer);
333 size_t start = 0;
334 size_t read_bytes = 0;
335 char* line = nullptr;
336 bool read_complete = false;
337 while (true) {
338 ssize_t bytes =
339 TEMP_FAILURE_RETRY(read(fd, char_buffer + read_bytes, buffer_size - read_bytes - 1));
340 if (bytes <= 0) {
341 if (read_bytes == 0) {
342 close(fd);
343 return bytes == 0;
344 }
345 // Treat the last piece of data as the last line.
346 char_buffer[start + read_bytes] = '\n';
347 bytes = 1;
348 read_complete = true;
349 }
350 read_bytes += bytes;
351
352 while (read_bytes > 0) {
353 char* newline = reinterpret_cast<char*>(memchr(&char_buffer[start], '\n', read_bytes));
354 if (newline == nullptr) {
355 break;
356 }
357 *newline = '\0';
358 line = &char_buffer[start];
359 start = newline - char_buffer + 1;
360 read_bytes -= newline - line + 1;
361
362 // Ignore the return code, errors are okay.
363 ReadMapFileContent(line, callback);
364 }
365
366 if (read_complete) {
367 close(fd);
368 return true;
369 }
370
371 if (start == 0 && read_bytes == buffer_size - 1) {
372 // The buffer provided is too small to contain this line, give up
373 // and indicate failure.
374 close(fd);
375 return false;
376 }
377
378 // Copy any leftover data to the front of the buffer.
379 if (start > 0) {
380 if (read_bytes > 0) {
381 memmove(char_buffer, &char_buffer[start], read_bytes);
382 }
383 start = 0;
384 }
385 }
386 }
387
388 /**
389 * A file memory mapping can be created such that it is only partially
390 * backed by the underlying file. i.e. the mapping size is larger than
391 * the file size.
392 *
393 * On builds that support larger than 4KB page-size, the common assumption
394 * that a file mapping is entirely backed by the underlying file, is
395 * more likely to be false.
396 *
397 * If an access to a region of the mapping beyond the end of the file
398 * occurs, there are 2 situations:
399 * 1) The access is between the end of the file and the next page
400 * boundary. The kernel will facilitate this although there is
401 * no file here.
402 * Note: That writing this region does not persist any data to
403 * the actual backing file.
404 * 2) The access is beyond the first page boundary after the end
405 * of the file. This will cause a filemap_fault which does not
406 * correspond to a valid file offset and the kernel will return
407 * a SIGBUS.
408 * See return value SIGBUS at:
409 * https://man7.org/linux/man-pages/man2/mmap.2.html#RETURN_VALUE
410 *
411 * Userspace programs that parse /proc/<pid>/maps or /proc/<pid>/smaps
412 * to determine the extent of memory mappings which they then use as
413 * arguments to other syscalls or directly access; should be aware of
414 * the second case above (2) and not assume that file mappings are
415 * entirely back by the underlying file.
416 *
417 * This is especially important for operations that would cause a
418 * page-fault on the range described in (2). In this case userspace
419 * should either handle the signal or use the range backed by the
420 * underlying file for the desired operation.
421 *
422 *
423 * MappedFileSize() - Returns the size of the memory map backed
424 * by the underlying file; or 0 if not file-backed.
425 * @start_addr - start address of the memory map.
426 * @end_addr - end address of the memory map.
427 * @file_offset - file offset of the backing file corresponding to the
428 * start of the memory map.
429 * @file_size - size of the file (<file_path>) in bytes.
430 *
431 * NOTE: The arguments corresponds to the following fields of an entry
432 * in /proc/<pid>/maps:
433 *
434 * <start_addr>-< end_addr > ... <file_offset> ... ... <file_path>
435 * 790b07dc6000-790b07dd9000 r--p 00000000 fe:09 21068208 /system/lib64/foo.so
436 *
437 * NOTE: Clients of this API should be aware that, although unlikely,
438 * it is possible for @file_size to change under us and race with
439 * the checks in MappedFileSize().
440 * Users should avoid concurrent modifications of @file_size, or
441 * use appropriate locking according to the usecase.
442 */
MappedFileSize(uint64_t start_addr,uint64_t end_addr,uint64_t file_offset,uint64_t file_size)443 inline uint64_t MappedFileSize(uint64_t start_addr, uint64_t end_addr,
444 uint64_t file_offset, uint64_t file_size) {
445 uint64_t len = end_addr - start_addr;
446
447 // This VMA may have been split from a larger file mapping; or the
448 // file may have been resized since the mapping was created.
449 if (file_offset > file_size) {
450 return 0;
451 }
452
453 // Mapping exceeds file_size ?
454 if ((file_offset + len) > file_size) {
455 return file_size - file_offset;
456 }
457
458 return len;
459 }
460
461 /*
462 * MappedFileSize() - Returns the size of the memory map backed
463 * by the underlying file; or 0 if not file-backed.
464 */
MappedFileSize(const MapInfo & map)465 inline uint64_t MappedFileSize(const MapInfo& map) {
466 // Anon mapping or device?
467 if (map.name.empty() || map.name[0] != '/' ||
468 android::base::StartsWith(map.name, "/dev/")) {
469 return 0;
470 }
471
472 struct stat file_stat;
473 if (stat(map.name.c_str(), &file_stat) != 0) {
474 return 0;
475 }
476
477 return MappedFileSize(map.start, map.end, map.pgoff, file_stat.st_size);
478 }
479
480 } /* namespace procinfo */
481 } /* namespace android */
482