blob: a8602b694c32e1c7d156735090c337eb79bcc6b5 [file] [log] [blame]
Avi Drissmane4622aa2022-09-08 20:36:061// Copyright 2013 The Chromium Authors
[email protected]959a8bf2013-07-03 02:02:232// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/debug/proc_maps_linux.h"
6
[email protected]35c80832013-09-06 05:07:507#include <fcntl.h>
aviebe805c2015-12-24 08:20:288#include <stddef.h>
Lei Zhang502b6c62025-11-09 09:50:129#include <unistd.h>
[email protected]959a8bf2013-07-03 02:02:2310
Jan Keitel4f69e2c2025-12-02 18:45:4011#include <string_view>
Takuto Ikuta4d4c6852024-12-09 11:16:0212#include <unordered_map>
13
Arthur Sonzogni44769c82025-12-12 16:01:4214#include "base/compiler_specific.h"
[email protected]42f558fd2014-03-17 19:02:3515#include "base/files/scoped_file.h"
Thiabaud Engelbrecht309fb1ea2024-10-29 15:35:1916#include "base/format_macros.h"
Hans Wennborg9f3bb63d2020-04-21 11:12:3817#include "base/logging.h"
Thiabaud Engelbrecht309fb1ea2024-10-29 15:35:1918#include "base/memory/page_size.h"
Lei Zhang502b6c62025-11-09 09:50:1219#include "base/posix/eintr_wrapper.h"
[email protected]959a8bf2013-07-03 02:02:2320#include "base/strings/string_split.h"
aviebe805c2015-12-24 08:20:2821#include "build/build_config.h"
22
Xiaohan Wang131aa4d2022-01-15 19:39:4123#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
aviebe805c2015-12-24 08:20:2824#include <inttypes.h>
25#endif
[email protected]959a8bf2013-07-03 02:02:2326
Thiabaud Engelbrechtf815dc4c2024-10-31 02:17:0927namespace base::debug {
[email protected]959a8bf2013-07-03 02:02:2328
Thiabaud Engelbrechta383afc2024-10-10 20:59:4929MappedMemoryRegion::MappedMemoryRegion() = default;
30MappedMemoryRegion::MappedMemoryRegion(const MappedMemoryRegion&) = default;
Thiabaud Engelbrechtf815dc4c2024-10-31 02:17:0931MappedMemoryRegion::MappedMemoryRegion(MappedMemoryRegion&&) noexcept = default;
Thiabaud Engelbrecht7e160bc2024-12-09 17:57:4832MappedMemoryRegion& MappedMemoryRegion::operator=(MappedMemoryRegion&) =
33 default;
34MappedMemoryRegion& MappedMemoryRegion::operator=(
35 MappedMemoryRegion&&) noexcept = default;
Thiabaud Engelbrechta383afc2024-10-10 20:59:4936
[email protected]35c80832013-09-06 05:07:5037// Scans |proc_maps| starting from |pos| returning true if the gate VMA was
38// found, otherwise returns false.
39static bool ContainsGateVMA(std::string* proc_maps, size_t pos) {
40#if defined(ARCH_CPU_ARM_FAMILY)
41 // The gate VMA on ARM kernels is the interrupt vectors page.
42 return proc_maps->find(" [vectors]\n", pos) != std::string::npos;
43#elif defined(ARCH_CPU_X86_64)
44 // The gate VMA on x86 64-bit kernels is the virtual system call page.
45 return proc_maps->find(" [vsyscall]\n", pos) != std::string::npos;
46#else
47 // Otherwise assume there is no gate VMA in which case we shouldn't
48 // get duplicate entires.
49 return false;
50#endif
51}
52
[email protected]959a8bf2013-07-03 02:02:2353bool ReadProcMaps(std::string* proc_maps) {
[email protected]35c80832013-09-06 05:07:5054 // seq_file only writes out a page-sized amount on each call. Refer to header
55 // file for details.
Peter Kastinga630c5fa2022-07-05 16:33:0756 const size_t read_size = static_cast<size_t>(sysconf(_SC_PAGESIZE));
[email protected]35c80832013-09-06 05:07:5057
[email protected]42f558fd2014-03-17 19:02:3558 base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY)));
59 if (!fd.is_valid()) {
[email protected]35c80832013-09-06 05:07:5060 DPLOG(ERROR) << "Couldn't open /proc/self/maps";
61 return false;
62 }
[email protected]35c80832013-09-06 05:07:5063 proc_maps->clear();
64
65 while (true) {
66 // To avoid a copy, resize |proc_maps| so read() can write directly into it.
67 // Compute |buffer| afterwards since resize() may reallocate.
68 size_t pos = proc_maps->size();
Peter Kastinga630c5fa2022-07-05 16:33:0769 proc_maps->resize(pos + read_size);
[email protected]35c80832013-09-06 05:07:5070 void* buffer = &(*proc_maps)[pos];
71
Peter Kastinga630c5fa2022-07-05 16:33:0772 ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, read_size));
[email protected]35c80832013-09-06 05:07:5073 if (bytes_read < 0) {
74 DPLOG(ERROR) << "Couldn't read /proc/self/maps";
75 proc_maps->clear();
76 return false;
77 }
78
79 // ... and don't forget to trim off excess bytes.
Peter Kastinga630c5fa2022-07-05 16:33:0780 proc_maps->resize(pos + static_cast<size_t>(bytes_read));
[email protected]35c80832013-09-06 05:07:5081
Peter Kasting134ef9af2024-12-28 02:30:0982 if (bytes_read == 0) {
[email protected]35c80832013-09-06 05:07:5083 break;
Peter Kasting134ef9af2024-12-28 02:30:0984 }
[email protected]35c80832013-09-06 05:07:5085
86 // The gate VMA is handled as a special case after seq_file has finished
87 // iterating through all entries in the virtual memory table.
88 //
89 // Unfortunately, if additional entries are added at this point in time
90 // seq_file gets confused and the next call to read() will return duplicate
91 // entries including the gate VMA again.
92 //
93 // Avoid this by searching for the gate VMA and breaking early.
Peter Kasting134ef9af2024-12-28 02:30:0994 if (ContainsGateVMA(proc_maps, pos)) {
[email protected]35c80832013-09-06 05:07:5095 break;
Peter Kasting134ef9af2024-12-28 02:30:0996 }
[email protected]35c80832013-09-06 05:07:5097 }
98
99 return true;
[email protected]959a8bf2013-07-03 02:02:23100}
101
Jan Keitel4f69e2c2025-12-02 18:45:40102bool ParseProcMaps(std::string_view input,
[email protected]959a8bf2013-07-03 02:02:23103 std::vector<MappedMemoryRegion>* regions_out) {
[email protected]ad9a0122014-03-22 00:34:52104 CHECK(regions_out);
[email protected]959a8bf2013-07-03 02:02:23105 std::vector<MappedMemoryRegion> regions;
106
107 // This isn't async safe nor terribly efficient, but it doesn't need to be at
108 // this point in time.
Peter Kasting134ef9af2024-12-28 02:30:09109 std::vector<std::string> lines =
110 SplitString(input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
[email protected]959a8bf2013-07-03 02:02:23111
112 for (size_t i = 0; i < lines.size(); ++i) {
113 // Due to splitting on '\n' the last line should be empty.
114 if (i == lines.size() - 1) {
[email protected]ad9a0122014-03-22 00:34:52115 if (!lines[i].empty()) {
116 DLOG(WARNING) << "Last line not empty";
[email protected]959a8bf2013-07-03 02:02:23117 return false;
[email protected]ad9a0122014-03-22 00:34:52118 }
[email protected]959a8bf2013-07-03 02:02:23119 break;
120 }
121
122 MappedMemoryRegion region;
123 const char* line = lines[i].c_str();
Arthur Sonzogni299864be12024-12-04 16:52:02124 char permissions[5] = {}; // Ensure NUL-terminated string.
aviebe805c2015-12-24 08:20:28125 uint8_t dev_major = 0;
126 uint8_t dev_minor = 0;
[email protected]959a8bf2013-07-03 02:02:23127 long inode = 0;
128 int path_index = 0;
129
130 // Sample format from man 5 proc:
131 //
132 // address perms offset dev inode pathname
133 // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm
134 //
135 // The final %n term captures the offset in the input string, which is used
136 // to determine the path name. It *does not* increment the return value.
137 // Refer to man 3 sscanf for details.
Arthur Sonzogni44769c82025-12-12 16:01:42138 if (UNSAFE_TODO(
139 sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n",
140 &region.start, &region.end, permissions, &region.offset,
141 &dev_major, &dev_minor, &inode, &path_index)) < 7) {
[email protected]ad9a0122014-03-22 00:34:52142 DPLOG(WARNING) << "sscanf failed for line: " << line;
[email protected]959a8bf2013-07-03 02:02:23143 return false;
144 }
145
Thiabaud Engelbrechta383afc2024-10-10 20:59:49146 region.inode = inode;
147 region.dev_major = dev_major;
148 region.dev_minor = dev_minor;
149
[email protected]959a8bf2013-07-03 02:02:23150 region.permissions = 0;
151
Peter Kasting134ef9af2024-12-28 02:30:09152 if (permissions[0] == 'r') {
[email protected]959a8bf2013-07-03 02:02:23153 region.permissions |= MappedMemoryRegion::READ;
Peter Kasting134ef9af2024-12-28 02:30:09154 } else if (permissions[0] != '-') {
[email protected]959a8bf2013-07-03 02:02:23155 return false;
Peter Kasting134ef9af2024-12-28 02:30:09156 }
[email protected]959a8bf2013-07-03 02:02:23157
Peter Kasting134ef9af2024-12-28 02:30:09158 if (permissions[1] == 'w') {
[email protected]959a8bf2013-07-03 02:02:23159 region.permissions |= MappedMemoryRegion::WRITE;
Peter Kasting134ef9af2024-12-28 02:30:09160 } else if (permissions[1] != '-') {
[email protected]959a8bf2013-07-03 02:02:23161 return false;
Peter Kasting134ef9af2024-12-28 02:30:09162 }
[email protected]959a8bf2013-07-03 02:02:23163
Peter Kasting134ef9af2024-12-28 02:30:09164 if (permissions[2] == 'x') {
[email protected]959a8bf2013-07-03 02:02:23165 region.permissions |= MappedMemoryRegion::EXECUTE;
Peter Kasting134ef9af2024-12-28 02:30:09166 } else if (permissions[2] != '-') {
[email protected]959a8bf2013-07-03 02:02:23167 return false;
Peter Kasting134ef9af2024-12-28 02:30:09168 }
[email protected]959a8bf2013-07-03 02:02:23169
Peter Kasting134ef9af2024-12-28 02:30:09170 if (permissions[3] == 'p') {
[email protected]959a8bf2013-07-03 02:02:23171 region.permissions |= MappedMemoryRegion::PRIVATE;
Peter Kasting134ef9af2024-12-28 02:30:09172 } else if (permissions[3] != 's' &&
173 permissions[3] != 'S') { // Shared memory.
[email protected]959a8bf2013-07-03 02:02:23174 return false;
Peter Kasting134ef9af2024-12-28 02:30:09175 }
[email protected]959a8bf2013-07-03 02:02:23176
177 // Pushing then assigning saves us a string copy.
178 regions.push_back(region);
Arthur Sonzogni44769c82025-12-12 16:01:42179 regions.back().path.assign(UNSAFE_TODO(line + path_index));
[email protected]959a8bf2013-07-03 02:02:23180 }
181
182 regions_out->swap(regions);
183 return true;
184}
185
Thiabaud Engelbrecht309fb1ea2024-10-29 15:35:19186std::optional<SmapsRollup> ParseSmapsRollup(const std::string& buffer) {
187 std::vector<std::string> lines =
188 SplitString(buffer, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
189
Joe Mason2bd833a2025-12-19 16:08:09190 std::unordered_map<std::string, ByteSize> tmp;
Thiabaud Engelbrecht309fb1ea2024-10-29 15:35:19191 for (const auto& line : lines) {
192 // This should be more than enough space for any output we get (but we also
193 // verify the size below).
194 std::string key;
195 key.resize(100);
196 size_t val;
Arthur Sonzogni44769c82025-12-12 16:01:42197 if (UNSAFE_TODO(sscanf(line.c_str(), "%99s %" PRIuS " kB", key.data(),
198 &val)) == 2) {
Thiabaud Engelbrecht309fb1ea2024-10-29 15:35:19199 // sscanf writes a nul-byte at the end of the result, so |strlen| is safe
200 // here. |resize| does not count the length of the nul-byte, and we want
201 // to trim off the trailing colon at the end, so we use |strlen - 1| here.
202 key.resize(strlen(key.c_str()) - 1);
Joe Mason2bd833a2025-12-19 16:08:09203 tmp[key] = KiBU(val);
Thiabaud Engelbrecht309fb1ea2024-10-29 15:35:19204 }
205 }
206
207 SmapsRollup smaps_rollup;
208
209 smaps_rollup.rss = tmp["Rss"];
210 smaps_rollup.pss = tmp["Pss"];
211 smaps_rollup.pss_anon = tmp["Pss_Anon"];
212 smaps_rollup.pss_file = tmp["Pss_File"];
213 smaps_rollup.pss_shmem = tmp["Pss_Shmem"];
214 smaps_rollup.private_dirty = tmp["Private_Dirty"];
215 smaps_rollup.swap = tmp["Swap"];
216 smaps_rollup.swap_pss = tmp["SwapPss"];
217
218 return smaps_rollup;
219}
220
221std::optional<SmapsRollup> ReadAndParseSmapsRollup() {
222 const size_t read_size = base::GetPageSize();
223
224 base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/smaps_rollup", O_RDONLY)));
225 if (!fd.is_valid()) {
226 DPLOG(ERROR) << "Couldn't open /proc/self/smaps_rollup";
227 return std::nullopt;
228 }
229
230 std::string buffer;
231 buffer.resize(read_size);
232
233 ssize_t bytes_read = HANDLE_EINTR(
234 read(fd.get(), static_cast<void*>(buffer.data()), read_size));
235 if (bytes_read < 0) {
236 DPLOG(ERROR) << "Couldn't read /proc/self/smaps_rollup";
237 return std::nullopt;
238 }
239
240 // We expect to read a few hundred bytes, which should be significantly less
241 // the page size.
242 DCHECK(static_cast<size_t>(bytes_read) < read_size);
243
244 buffer.resize(static_cast<size_t>(bytes_read));
245
246 return ParseSmapsRollup(buffer);
247}
248
249std::optional<SmapsRollup> ParseSmapsRollupForTesting(
250 const std::string& smaps_rollup) {
251 return ParseSmapsRollup(smaps_rollup);
252}
253
Thiabaud Engelbrechtf815dc4c2024-10-31 02:17:09254} // namespace base::debug