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