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 ®ion.start, ®ion.end, permissions, ®ion.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