• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "base/debug/proc_maps_linux.h"
11 
12 #include <fcntl.h>
13 #include <stddef.h>
14 
15 #include "base/files/file_util.h"
16 #include "base/files/scoped_file.h"
17 #include "base/format_macros.h"
18 #include "base/logging.h"
19 #include "base/memory/page_size.h"
20 #include "base/strings/string_split.h"
21 #include "build/build_config.h"
22 
23 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
24 #include <inttypes.h>
25 #endif
26 
27 namespace base::debug {
28 
29 MappedMemoryRegion::MappedMemoryRegion() = default;
30 MappedMemoryRegion::MappedMemoryRegion(const MappedMemoryRegion&) = default;
31 MappedMemoryRegion::MappedMemoryRegion(MappedMemoryRegion&&) noexcept = default;
32 
33 // Scans |proc_maps| starting from |pos| returning true if the gate VMA was
34 // found, otherwise returns false.
ContainsGateVMA(std::string * proc_maps,size_t pos)35 static bool ContainsGateVMA(std::string* proc_maps, size_t pos) {
36 #if defined(ARCH_CPU_ARM_FAMILY)
37   // The gate VMA on ARM kernels is the interrupt vectors page.
38   return proc_maps->find(" [vectors]\n", pos) != std::string::npos;
39 #elif defined(ARCH_CPU_X86_64)
40   // The gate VMA on x86 64-bit kernels is the virtual system call page.
41   return proc_maps->find(" [vsyscall]\n", pos) != std::string::npos;
42 #else
43   // Otherwise assume there is no gate VMA in which case we shouldn't
44   // get duplicate entires.
45   return false;
46 #endif
47 }
48 
ReadProcMaps(std::string * proc_maps)49 bool ReadProcMaps(std::string* proc_maps) {
50   // seq_file only writes out a page-sized amount on each call. Refer to header
51   // file for details.
52   const size_t read_size = static_cast<size_t>(sysconf(_SC_PAGESIZE));
53 
54   base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY)));
55   if (!fd.is_valid()) {
56     DPLOG(ERROR) << "Couldn't open /proc/self/maps";
57     return false;
58   }
59   proc_maps->clear();
60 
61   while (true) {
62     // To avoid a copy, resize |proc_maps| so read() can write directly into it.
63     // Compute |buffer| afterwards since resize() may reallocate.
64     size_t pos = proc_maps->size();
65     proc_maps->resize(pos + read_size);
66     void* buffer = &(*proc_maps)[pos];
67 
68     ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, read_size));
69     if (bytes_read < 0) {
70       DPLOG(ERROR) << "Couldn't read /proc/self/maps";
71       proc_maps->clear();
72       return false;
73     }
74 
75     // ... and don't forget to trim off excess bytes.
76     proc_maps->resize(pos + static_cast<size_t>(bytes_read));
77 
78     if (bytes_read == 0)
79       break;
80 
81     // The gate VMA is handled as a special case after seq_file has finished
82     // iterating through all entries in the virtual memory table.
83     //
84     // Unfortunately, if additional entries are added at this point in time
85     // seq_file gets confused and the next call to read() will return duplicate
86     // entries including the gate VMA again.
87     //
88     // Avoid this by searching for the gate VMA and breaking early.
89     if (ContainsGateVMA(proc_maps, pos))
90       break;
91   }
92 
93   return true;
94 }
95 
ParseProcMaps(const std::string & input,std::vector<MappedMemoryRegion> * regions_out)96 bool ParseProcMaps(const std::string& input,
97                    std::vector<MappedMemoryRegion>* regions_out) {
98   CHECK(regions_out);
99   std::vector<MappedMemoryRegion> regions;
100 
101   // This isn't async safe nor terribly efficient, but it doesn't need to be at
102   // this point in time.
103   std::vector<std::string> lines = SplitString(
104       input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
105 
106   for (size_t i = 0; i < lines.size(); ++i) {
107     // Due to splitting on '\n' the last line should be empty.
108     if (i == lines.size() - 1) {
109       if (!lines[i].empty()) {
110         DLOG(WARNING) << "Last line not empty";
111         return false;
112       }
113       break;
114     }
115 
116     MappedMemoryRegion region;
117     const char* line = lines[i].c_str();
118     char permissions[5] = {'\0'};  // Ensure NUL-terminated string.
119     uint8_t dev_major = 0;
120     uint8_t dev_minor = 0;
121     long inode = 0;
122     int path_index = 0;
123 
124     // Sample format from man 5 proc:
125     //
126     // address           perms offset  dev   inode   pathname
127     // 08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm
128     //
129     // The final %n term captures the offset in the input string, which is used
130     // to determine the path name. It *does not* increment the return value.
131     // Refer to man 3 sscanf for details.
132     if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n",
133                &region.start, &region.end, permissions, &region.offset,
134                &dev_major, &dev_minor, &inode, &path_index) < 7) {
135       DPLOG(WARNING) << "sscanf failed for line: " << line;
136       return false;
137     }
138 
139     region.inode = inode;
140     region.dev_major = dev_major;
141     region.dev_minor = dev_minor;
142 
143     region.permissions = 0;
144 
145     if (permissions[0] == 'r')
146       region.permissions |= MappedMemoryRegion::READ;
147     else if (permissions[0] != '-')
148       return false;
149 
150     if (permissions[1] == 'w')
151       region.permissions |= MappedMemoryRegion::WRITE;
152     else if (permissions[1] != '-')
153       return false;
154 
155     if (permissions[2] == 'x')
156       region.permissions |= MappedMemoryRegion::EXECUTE;
157     else if (permissions[2] != '-')
158       return false;
159 
160     if (permissions[3] == 'p')
161       region.permissions |= MappedMemoryRegion::PRIVATE;
162     else if (permissions[3] != 's' && permissions[3] != 'S')  // Shared memory.
163       return false;
164 
165     // Pushing then assigning saves us a string copy.
166     regions.push_back(region);
167     regions.back().path.assign(line + path_index);
168   }
169 
170   regions_out->swap(regions);
171   return true;
172 }
173 
ParseSmapsRollup(const std::string & buffer)174 std::optional<SmapsRollup> ParseSmapsRollup(const std::string& buffer) {
175   std::vector<std::string> lines =
176       SplitString(buffer, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
177 
178   std::unordered_map<std::string, size_t> tmp;
179   for (const auto& line : lines) {
180     // This should be more than enough space for any output we get (but we also
181     // verify the size below).
182     std::string key;
183     key.resize(100);
184     size_t val;
185     if (sscanf(line.c_str(), "%99s %" PRIuS " kB", key.data(), &val) == 2) {
186       // sscanf writes a nul-byte at the end of the result, so |strlen| is safe
187       // here. |resize| does not count the length of the nul-byte, and we want
188       // to trim off the trailing colon at the end, so we use |strlen - 1| here.
189       key.resize(strlen(key.c_str()) - 1);
190       tmp[key] = val * 1024;
191     }
192   }
193 
194   SmapsRollup smaps_rollup;
195 
196   smaps_rollup.rss = tmp["Rss"];
197   smaps_rollup.pss = tmp["Pss"];
198   smaps_rollup.pss_anon = tmp["Pss_Anon"];
199   smaps_rollup.pss_file = tmp["Pss_File"];
200   smaps_rollup.pss_shmem = tmp["Pss_Shmem"];
201   smaps_rollup.private_dirty = tmp["Private_Dirty"];
202   smaps_rollup.swap = tmp["Swap"];
203   smaps_rollup.swap_pss = tmp["SwapPss"];
204 
205   return smaps_rollup;
206 }
207 
ReadAndParseSmapsRollup()208 std::optional<SmapsRollup> ReadAndParseSmapsRollup() {
209   const size_t read_size = base::GetPageSize();
210 
211   base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/smaps_rollup", O_RDONLY)));
212   if (!fd.is_valid()) {
213     DPLOG(ERROR) << "Couldn't open /proc/self/smaps_rollup";
214     return std::nullopt;
215   }
216 
217   std::string buffer;
218   buffer.resize(read_size);
219 
220   ssize_t bytes_read = HANDLE_EINTR(
221       read(fd.get(), static_cast<void*>(buffer.data()), read_size));
222   if (bytes_read < 0) {
223     DPLOG(ERROR) << "Couldn't read /proc/self/smaps_rollup";
224     return std::nullopt;
225   }
226 
227   // We expect to read a few hundred bytes, which should be significantly less
228   // the page size.
229   DCHECK(static_cast<size_t>(bytes_read) < read_size);
230 
231   buffer.resize(static_cast<size_t>(bytes_read));
232 
233   return ParseSmapsRollup(buffer);
234 }
235 
ParseSmapsRollupForTesting(const std::string & smaps_rollup)236 std::optional<SmapsRollup> ParseSmapsRollupForTesting(
237     const std::string& smaps_rollup) {
238   return ParseSmapsRollup(smaps_rollup);
239 }
240 
241 }  // namespace base::debug
242