| Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2013 The Chromium Authors |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 2 | // 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] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 7 | #include <fcntl.h> |
| avi | ebe805c | 2015-12-24 08:20:28 | [diff] [blame] | 8 | #include <stddef.h> |
| Lei Zhang | 502b6c6 | 2025-11-09 09:50:12 | [diff] [blame] | 9 | #include <unistd.h> |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 10 | |
| Jan Keitel | 4f69e2c | 2025-12-02 18:45:40 | [diff] [blame] | 11 | #include <string_view> |
| Takuto Ikuta | 4d4c685 | 2024-12-09 11:16:02 | [diff] [blame] | 12 | #include <unordered_map> |
| 13 | |
| Arthur Sonzogni | 44769c8 | 2025-12-12 16:01:42 | [diff] [blame] | 14 | #include "base/compiler_specific.h" |
| [email protected] | 42f558fd | 2014-03-17 19:02:35 | [diff] [blame] | 15 | #include "base/files/scoped_file.h" |
| Thiabaud Engelbrecht | 309fb1ea | 2024-10-29 15:35:19 | [diff] [blame] | 16 | #include "base/format_macros.h" |
| Hans Wennborg | 9f3bb63d | 2020-04-21 11:12:38 | [diff] [blame] | 17 | #include "base/logging.h" |
| Thiabaud Engelbrecht | 309fb1ea | 2024-10-29 15:35:19 | [diff] [blame] | 18 | #include "base/memory/page_size.h" |
| Lei Zhang | 502b6c6 | 2025-11-09 09:50:12 | [diff] [blame] | 19 | #include "base/posix/eintr_wrapper.h" |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 20 | #include "base/strings/string_split.h" |
| avi | ebe805c | 2015-12-24 08:20:28 | [diff] [blame] | 21 | #include "build/build_config.h" |
| 22 | |
| Xiaohan Wang | 131aa4d | 2022-01-15 19:39:41 | [diff] [blame] | 23 | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) |
| avi | ebe805c | 2015-12-24 08:20:28 | [diff] [blame] | 24 | #include <inttypes.h> |
| 25 | #endif |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 26 | |
| Thiabaud Engelbrecht | f815dc4c | 2024-10-31 02:17:09 | [diff] [blame] | 27 | namespace base::debug { |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 28 | |
| Thiabaud Engelbrecht | a383afc | 2024-10-10 20:59:49 | [diff] [blame] | 29 | MappedMemoryRegion::MappedMemoryRegion() = default; |
| 30 | MappedMemoryRegion::MappedMemoryRegion(const MappedMemoryRegion&) = default; |
| Thiabaud Engelbrecht | f815dc4c | 2024-10-31 02:17:09 | [diff] [blame] | 31 | MappedMemoryRegion::MappedMemoryRegion(MappedMemoryRegion&&) noexcept = default; |
| Thiabaud Engelbrecht | 7e160bc | 2024-12-09 17:57:48 | [diff] [blame] | 32 | MappedMemoryRegion& MappedMemoryRegion::operator=(MappedMemoryRegion&) = |
| 33 | default; |
| 34 | MappedMemoryRegion& MappedMemoryRegion::operator=( |
| 35 | MappedMemoryRegion&&) noexcept = default; |
| Thiabaud Engelbrecht | a383afc | 2024-10-10 20:59:49 | [diff] [blame] | 36 | |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 37 | // Scans |proc_maps| starting from |pos| returning true if the gate VMA was |
| 38 | // found, otherwise returns false. |
| 39 | static 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] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 53 | bool ReadProcMaps(std::string* proc_maps) { |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 54 | // seq_file only writes out a page-sized amount on each call. Refer to header |
| 55 | // file for details. |
| Peter Kasting | a630c5fa | 2022-07-05 16:33:07 | [diff] [blame] | 56 | const size_t read_size = static_cast<size_t>(sysconf(_SC_PAGESIZE)); |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 57 | |
| [email protected] | 42f558fd | 2014-03-17 19:02:35 | [diff] [blame] | 58 | base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY))); |
| 59 | if (!fd.is_valid()) { |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 60 | DPLOG(ERROR) << "Couldn't open /proc/self/maps"; |
| 61 | return false; |
| 62 | } |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 63 | 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 Kasting | a630c5fa | 2022-07-05 16:33:07 | [diff] [blame] | 69 | proc_maps->resize(pos + read_size); |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 70 | void* buffer = &(*proc_maps)[pos]; |
| 71 | |
| Peter Kasting | a630c5fa | 2022-07-05 16:33:07 | [diff] [blame] | 72 | ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, read_size)); |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 73 | 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 Kasting | a630c5fa | 2022-07-05 16:33:07 | [diff] [blame] | 80 | proc_maps->resize(pos + static_cast<size_t>(bytes_read)); |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 81 | |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 82 | if (bytes_read == 0) { |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 83 | break; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 84 | } |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 85 | |
| 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 Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 94 | if (ContainsGateVMA(proc_maps, pos)) { |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 95 | break; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 96 | } |
| [email protected] | 35c8083 | 2013-09-06 05:07:50 | [diff] [blame] | 97 | } |
| 98 | |
| 99 | return true; |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 100 | } |
| 101 | |
| Jan Keitel | 4f69e2c | 2025-12-02 18:45:40 | [diff] [blame] | 102 | bool ParseProcMaps(std::string_view input, |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 103 | std::vector<MappedMemoryRegion>* regions_out) { |
| [email protected] | ad9a012 | 2014-03-22 00:34:52 | [diff] [blame] | 104 | CHECK(regions_out); |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 105 | 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 Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 109 | std::vector<std::string> lines = |
| 110 | SplitString(input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 111 | |
| 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] | ad9a012 | 2014-03-22 00:34:52 | [diff] [blame] | 115 | if (!lines[i].empty()) { |
| 116 | DLOG(WARNING) << "Last line not empty"; |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 117 | return false; |
| [email protected] | ad9a012 | 2014-03-22 00:34:52 | [diff] [blame] | 118 | } |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 119 | break; |
| 120 | } |
| 121 | |
| 122 | MappedMemoryRegion region; |
| 123 | const char* line = lines[i].c_str(); |
| Arthur Sonzogni | 299864be1 | 2024-12-04 16:52:02 | [diff] [blame] | 124 | char permissions[5] = {}; // Ensure NUL-terminated string. |
| avi | ebe805c | 2015-12-24 08:20:28 | [diff] [blame] | 125 | uint8_t dev_major = 0; |
| 126 | uint8_t dev_minor = 0; |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 127 | 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 Sonzogni | 44769c8 | 2025-12-12 16:01:42 | [diff] [blame] | 138 | if (UNSAFE_TODO( |
| 139 | sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n", |
| 140 | ®ion.start, ®ion.end, permissions, ®ion.offset, |
| 141 | &dev_major, &dev_minor, &inode, &path_index)) < 7) { |
| [email protected] | ad9a012 | 2014-03-22 00:34:52 | [diff] [blame] | 142 | DPLOG(WARNING) << "sscanf failed for line: " << line; |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 143 | return false; |
| 144 | } |
| 145 | |
| Thiabaud Engelbrecht | a383afc | 2024-10-10 20:59:49 | [diff] [blame] | 146 | region.inode = inode; |
| 147 | region.dev_major = dev_major; |
| 148 | region.dev_minor = dev_minor; |
| 149 | |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 150 | region.permissions = 0; |
| 151 | |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 152 | if (permissions[0] == 'r') { |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 153 | region.permissions |= MappedMemoryRegion::READ; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 154 | } else if (permissions[0] != '-') { |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 155 | return false; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 156 | } |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 157 | |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 158 | if (permissions[1] == 'w') { |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 159 | region.permissions |= MappedMemoryRegion::WRITE; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 160 | } else if (permissions[1] != '-') { |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 161 | return false; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 162 | } |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 163 | |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 164 | if (permissions[2] == 'x') { |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 165 | region.permissions |= MappedMemoryRegion::EXECUTE; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 166 | } else if (permissions[2] != '-') { |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 167 | return false; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 168 | } |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 169 | |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 170 | if (permissions[3] == 'p') { |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 171 | region.permissions |= MappedMemoryRegion::PRIVATE; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 172 | } else if (permissions[3] != 's' && |
| 173 | permissions[3] != 'S') { // Shared memory. |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 174 | return false; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 175 | } |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 176 | |
| 177 | // Pushing then assigning saves us a string copy. |
| 178 | regions.push_back(region); |
| Arthur Sonzogni | 44769c8 | 2025-12-12 16:01:42 | [diff] [blame] | 179 | regions.back().path.assign(UNSAFE_TODO(line + path_index)); |
| [email protected] | 959a8bf | 2013-07-03 02:02:23 | [diff] [blame] | 180 | } |
| 181 | |
| 182 | regions_out->swap(regions); |
| 183 | return true; |
| 184 | } |
| 185 | |
| Thiabaud Engelbrecht | 309fb1ea | 2024-10-29 15:35:19 | [diff] [blame] | 186 | std::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 Mason | 2bd833a | 2025-12-19 16:08:09 | [diff] [blame] | 190 | std::unordered_map<std::string, ByteSize> tmp; |
| Thiabaud Engelbrecht | 309fb1ea | 2024-10-29 15:35:19 | [diff] [blame] | 191 | 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 Sonzogni | 44769c8 | 2025-12-12 16:01:42 | [diff] [blame] | 197 | if (UNSAFE_TODO(sscanf(line.c_str(), "%99s %" PRIuS " kB", key.data(), |
| 198 | &val)) == 2) { |
| Thiabaud Engelbrecht | 309fb1ea | 2024-10-29 15:35:19 | [diff] [blame] | 199 | // 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 Mason | 2bd833a | 2025-12-19 16:08:09 | [diff] [blame] | 203 | tmp[key] = KiBU(val); |
| Thiabaud Engelbrecht | 309fb1ea | 2024-10-29 15:35:19 | [diff] [blame] | 204 | } |
| 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 | |
| 221 | std::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 | |
| 249 | std::optional<SmapsRollup> ParseSmapsRollupForTesting( |
| 250 | const std::string& smaps_rollup) { |
| 251 | return ParseSmapsRollup(smaps_rollup); |
| 252 | } |
| 253 | |
| Thiabaud Engelbrecht | f815dc4c | 2024-10-31 02:17:09 | [diff] [blame] | 254 | } // namespace base::debug |