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