• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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