gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/util/proc_util.cc (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "test/util/proc_util.h" 16 17 #include <sys/prctl.h> 18 19 #include <algorithm> 20 #include <iostream> 21 #include <vector> 22 23 #include "absl/algorithm/container.h" 24 #include "absl/container/flat_hash_set.h" 25 #include "absl/strings/ascii.h" 26 #include "absl/strings/str_cat.h" 27 #include "absl/strings/str_split.h" 28 #include "absl/strings/string_view.h" 29 #include "test/util/fs_util.h" 30 #include "test/util/test_util.h" 31 32 namespace gvisor { 33 namespace testing { 34 35 // Parses a single line from /proc/<xxx>/maps. 36 PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line) { 37 ProcMapsEntry map_entry = {}; 38 39 // Limit splitting to 6 parts so that if there is a file path and it contains 40 // spaces, the file path is not split. 41 std::vector<std::string> parts = 42 absl::StrSplit(line, absl::MaxSplits(' ', 5), absl::SkipEmpty()); 43 44 // parts.size() should be 6 if there is a file name specified, and 5 45 // otherwise. 46 if (parts.size() < 5) { 47 return PosixError(EINVAL, absl::StrCat("Invalid line: ", line)); 48 } 49 50 // Address range in the form X-X where X are hex values without leading 0x. 51 std::vector<std::string> addresses = absl::StrSplit(parts[0], '-'); 52 if (addresses.size() != 2) { 53 return PosixError(EINVAL, 54 absl::StrCat("Invalid address range: ", parts[0])); 55 } 56 ASSIGN_OR_RETURN_ERRNO(map_entry.start, AtoiBase(addresses[0], 16)); 57 ASSIGN_OR_RETURN_ERRNO(map_entry.end, AtoiBase(addresses[1], 16)); 58 59 // Permissions are four bytes of the form rwxp or - if permission not set. 60 if (parts[1].size() != 4) { 61 return PosixError(EINVAL, 62 absl::StrCat("Invalid permission field: ", parts[1])); 63 } 64 65 map_entry.readable = parts[1][0] == 'r'; 66 map_entry.writable = parts[1][1] == 'w'; 67 map_entry.executable = parts[1][2] == 'x'; 68 map_entry.priv = parts[1][3] == 'p'; 69 70 ASSIGN_OR_RETURN_ERRNO(map_entry.offset, AtoiBase(parts[2], 16)); 71 72 std::vector<std::string> device = absl::StrSplit(parts[3], ':'); 73 if (device.size() != 2) { 74 return PosixError(EINVAL, absl::StrCat("Invalid device: ", parts[3])); 75 } 76 ASSIGN_OR_RETURN_ERRNO(map_entry.major, AtoiBase(device[0], 16)); 77 ASSIGN_OR_RETURN_ERRNO(map_entry.minor, AtoiBase(device[1], 16)); 78 79 ASSIGN_OR_RETURN_ERRNO(map_entry.inode, Atoi<int64_t>(parts[4])); 80 if (parts.size() == 6) { 81 // A filename is present. However, absl::StrSplit retained the whitespace 82 // between the inode number and the filename. 83 map_entry.filename = 84 std::string(absl::StripLeadingAsciiWhitespace(parts[5])); 85 } 86 87 return map_entry; 88 } 89 90 PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps( 91 absl::string_view contents) { 92 std::vector<ProcMapsEntry> entries; 93 auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); 94 for (const auto& l : lines) { 95 std::cout << "line: " << l << std::endl; 96 ASSIGN_OR_RETURN_ERRNO(auto entry, ParseProcMapsLine(l)); 97 entries.push_back(entry); 98 } 99 return entries; 100 } 101 102 PosixErrorOr<bool> IsVsyscallEnabled() { 103 ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/maps")); 104 ASSIGN_OR_RETURN_ERRNO(auto maps, ParseProcMaps(contents)); 105 return std::any_of(maps.begin(), maps.end(), [](const ProcMapsEntry& e) { 106 return e.filename == "[vsyscall]"; 107 }); 108 } 109 110 // Given the value part of a /proc/[pid]/smaps field containing a value in kB 111 // (for example, " 4 kB", returns the value in kB (in this example, 4). 112 PosixErrorOr<size_t> SmapsValueKb(absl::string_view value) { 113 // TODO(jamieliu): let us use RE2 or <regex> 114 std::pair<absl::string_view, absl::string_view> parts = 115 absl::StrSplit(value, ' ', absl::SkipEmpty()); 116 if (parts.second != "kB") { 117 return PosixError(EINVAL, 118 absl::StrCat("invalid smaps field value: ", value)); 119 } 120 ASSIGN_OR_RETURN_ERRNO(auto val_kb, Atoi<size_t>(parts.first)); 121 return val_kb; 122 } 123 124 PosixErrorOr<std::vector<ProcSmapsEntry>> ParseProcSmaps( 125 absl::string_view contents) { 126 std::vector<ProcSmapsEntry> entries; 127 absl::optional<ProcSmapsEntry> entry; 128 bool have_size_kb = false; 129 bool have_rss_kb = false; 130 bool have_shared_clean_kb = false; 131 bool have_shared_dirty_kb = false; 132 bool have_private_clean_kb = false; 133 bool have_private_dirty_kb = false; 134 135 auto const finish_entry = [&] { 136 if (entry) { 137 if (!have_size_kb) { 138 return PosixError(EINVAL, "smaps entry is missing Size"); 139 } 140 if (!have_rss_kb) { 141 return PosixError(EINVAL, "smaps entry is missing Rss"); 142 } 143 if (!have_shared_clean_kb) { 144 return PosixError(EINVAL, "smaps entry is missing Shared_Clean"); 145 } 146 if (!have_shared_dirty_kb) { 147 return PosixError(EINVAL, "smaps entry is missing Shared_Dirty"); 148 } 149 if (!have_private_clean_kb) { 150 return PosixError(EINVAL, "smaps entry is missing Private_Clean"); 151 } 152 if (!have_private_dirty_kb) { 153 return PosixError(EINVAL, "smaps entry is missing Private_Dirty"); 154 } 155 // std::move(entry.value()) instead of std::move(entry).value(), because 156 // otherwise tools may report a "use-after-move" warning, which is 157 // spurious because entry.emplace() below resets entry to a new 158 // ProcSmapsEntry. 159 entries.emplace_back(std::move(entry.value())); 160 } 161 entry.emplace(); 162 have_size_kb = false; 163 have_rss_kb = false; 164 have_shared_clean_kb = false; 165 have_shared_dirty_kb = false; 166 have_private_clean_kb = false; 167 have_private_dirty_kb = false; 168 return NoError(); 169 }; 170 171 // Holds key/value pairs from smaps field lines. Declared here so it can be 172 // captured by reference by the following lambdas. 173 std::vector<absl::string_view> key_value; 174 175 auto const on_required_field_kb = [&](size_t* field, bool* have_field) { 176 if (*have_field) { 177 return PosixError( 178 EINVAL, 179 absl::StrFormat("smaps entry has duplicate %s line", key_value[0])); 180 } 181 ASSIGN_OR_RETURN_ERRNO(*field, SmapsValueKb(key_value[1])); 182 *have_field = true; 183 return NoError(); 184 }; 185 186 auto const on_optional_field_kb = [&](absl::optional<size_t>* field) { 187 if (*field) { 188 return PosixError( 189 EINVAL, 190 absl::StrFormat("smaps entry has duplicate %s line", key_value[0])); 191 } 192 ASSIGN_OR_RETURN_ERRNO(*field, SmapsValueKb(key_value[1])); 193 return NoError(); 194 }; 195 196 absl::flat_hash_set<std::string> unknown_fields; 197 auto const on_unknown_field = [&] { 198 absl::string_view key = key_value[0]; 199 // Don't mention unknown fields more than once. 200 if (unknown_fields.count(key)) { 201 return; 202 } 203 unknown_fields.insert(std::string(key)); 204 std::cerr << "skipping unknown smaps field " << key << std::endl; 205 }; 206 207 auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); 208 for (absl::string_view l : lines) { 209 // Is this line a valid /proc/[pid]/maps entry? 210 auto maybe_maps_entry = ParseProcMapsLine(l); 211 if (maybe_maps_entry.ok()) { 212 // This marks the beginning of a new /proc/[pid]/smaps entry. 213 RETURN_IF_ERRNO(finish_entry()); 214 entry->maps_entry = std::move(maybe_maps_entry).ValueOrDie(); 215 continue; 216 } 217 // Otherwise it's a field in an existing /proc/[pid]/smaps entry of the form 218 // "key:value" (where value in practice will be preceded by a variable 219 // amount of whitespace). 220 if (!entry) { 221 std::cerr << "smaps line not considered a maps line: " 222 << maybe_maps_entry.error().message() << std::endl; 223 return PosixError( 224 EINVAL, 225 absl::StrCat("smaps field line without preceding maps line: ", l)); 226 } 227 key_value = absl::StrSplit(l, absl::MaxSplits(':', 1)); 228 if (key_value.size() != 2) { 229 return PosixError(EINVAL, absl::StrCat("invalid smaps field line: ", l)); 230 } 231 absl::string_view const key = key_value[0]; 232 if (key == "Size") { 233 RETURN_IF_ERRNO(on_required_field_kb(&entry->size_kb, &have_size_kb)); 234 } else if (key == "Rss") { 235 RETURN_IF_ERRNO(on_required_field_kb(&entry->rss_kb, &have_rss_kb)); 236 } else if (key == "Shared_Clean") { 237 RETURN_IF_ERRNO( 238 on_required_field_kb(&entry->shared_clean_kb, &have_shared_clean_kb)); 239 } else if (key == "Shared_Dirty") { 240 RETURN_IF_ERRNO( 241 on_required_field_kb(&entry->shared_dirty_kb, &have_shared_dirty_kb)); 242 } else if (key == "Private_Clean") { 243 RETURN_IF_ERRNO(on_required_field_kb(&entry->private_clean_kb, 244 &have_private_clean_kb)); 245 } else if (key == "Private_Dirty") { 246 RETURN_IF_ERRNO(on_required_field_kb(&entry->private_dirty_kb, 247 &have_private_dirty_kb)); 248 } else if (key == "Pss") { 249 RETURN_IF_ERRNO(on_optional_field_kb(&entry->pss_kb)); 250 } else if (key == "Referenced") { 251 RETURN_IF_ERRNO(on_optional_field_kb(&entry->referenced_kb)); 252 } else if (key == "Anonymous") { 253 RETURN_IF_ERRNO(on_optional_field_kb(&entry->anonymous_kb)); 254 } else if (key == "AnonHugePages") { 255 RETURN_IF_ERRNO(on_optional_field_kb(&entry->anon_huge_pages_kb)); 256 } else if (key == "Shared_Hugetlb") { 257 RETURN_IF_ERRNO(on_optional_field_kb(&entry->shared_hugetlb_kb)); 258 } else if (key == "Private_Hugetlb") { 259 RETURN_IF_ERRNO(on_optional_field_kb(&entry->private_hugetlb_kb)); 260 } else if (key == "Swap") { 261 RETURN_IF_ERRNO(on_optional_field_kb(&entry->swap_kb)); 262 } else if (key == "SwapPss") { 263 RETURN_IF_ERRNO(on_optional_field_kb(&entry->swap_pss_kb)); 264 } else if (key == "KernelPageSize") { 265 RETURN_IF_ERRNO(on_optional_field_kb(&entry->kernel_page_size_kb)); 266 } else if (key == "MMUPageSize") { 267 RETURN_IF_ERRNO(on_optional_field_kb(&entry->mmu_page_size_kb)); 268 } else if (key == "Locked") { 269 RETURN_IF_ERRNO(on_optional_field_kb(&entry->locked_kb)); 270 } else if (key == "VmFlags") { 271 if (entry->vm_flags) { 272 return PosixError(EINVAL, "duplicate VmFlags line"); 273 } 274 entry->vm_flags = absl::StrSplit(key_value[1], ' ', absl::SkipEmpty()); 275 } else { 276 on_unknown_field(); 277 } 278 } 279 RETURN_IF_ERRNO(finish_entry()); 280 return entries; 281 } 282 283 PosixErrorOr<ProcSmapsEntry> FindUniqueSmapsEntry( 284 std::vector<ProcSmapsEntry> const& entries, uintptr_t addr) { 285 auto const pred = [&](ProcSmapsEntry const& entry) { 286 return entry.maps_entry.start <= addr && addr < entry.maps_entry.end; 287 }; 288 auto const it = absl::c_find_if(entries, pred); 289 if (it == entries.end()) { 290 return PosixError(EINVAL, 291 absl::StrFormat("no entry contains address %#x", addr)); 292 } 293 auto const it2 = std::find_if(it + 1, entries.end(), pred); 294 if (it2 != entries.end()) { 295 return PosixError( 296 EINVAL, 297 absl::StrFormat("overlapping entries [%#x-%#x) and [%#x-%#x) both " 298 "contain address %#x", 299 it->maps_entry.start, it->maps_entry.end, 300 it2->maps_entry.start, it2->maps_entry.end, addr)); 301 } 302 return *it; 303 } 304 305 PosixErrorOr<std::vector<ProcSmapsEntry>> ReadProcSelfSmaps() { 306 ASSIGN_OR_RETURN_ERRNO(std::string contents, GetContents("/proc/self/smaps")); 307 return ParseProcSmaps(contents); 308 } 309 310 PosixErrorOr<std::vector<ProcSmapsEntry>> ReadProcSmaps(pid_t pid) { 311 ASSIGN_OR_RETURN_ERRNO(std::string contents, 312 GetContents(absl::StrCat("/proc/", pid, "/smaps"))); 313 return ParseProcSmaps(contents); 314 } 315 316 bool EntryHasNH(const ProcSmapsEntry& e) { 317 if (e.vm_flags) { 318 auto flags = e.vm_flags.value(); 319 return std::find(flags.begin(), flags.end(), "nh") != flags.end(); 320 } 321 return false; 322 } 323 324 bool StackTHPDisabled(std::vector<ProcSmapsEntry> maps) { 325 return std::any_of(maps.begin(), maps.end(), [](const ProcSmapsEntry& e) { 326 return e.maps_entry.filename == "[stack]" && EntryHasNH(e); 327 }); 328 } 329 330 bool IsTHPDisabled() { 331 auto maps = ReadProcSelfSmaps(); 332 return StackTHPDisabled(maps.ValueOrDie()); 333 } 334 335 std::string LimitTypeToString(LimitType type) { 336 switch (type) { 337 case LimitType::kCPU: 338 return "cpu time"; 339 case LimitType::kFileSize: 340 return "file size"; 341 case LimitType::kData: 342 return "data size"; 343 case LimitType::kStack: 344 return "stack size"; 345 case LimitType::kCore: 346 return "core file size"; 347 case LimitType::kRSS: 348 return "resident set"; 349 case LimitType::kProcessCount: 350 return "processes"; 351 case LimitType::kNumberOfFiles: 352 return "open files"; 353 case LimitType::kMemoryLocked: 354 return "locked memory"; 355 case LimitType::kAS: 356 return "address space"; 357 case LimitType::kLocks: 358 return "file locks"; 359 case LimitType::kSignalsPending: 360 return "pending signals"; 361 case LimitType::kMessageQueueBytes: 362 return "msgqueue size"; 363 case LimitType::kNice: 364 return "nice priority"; 365 case LimitType::kRealTimePriority: 366 return "realtime priority"; 367 case LimitType::kRttime: 368 return "realtime timeout"; 369 default: 370 return "unknown"; 371 } 372 } 373 374 PosixErrorOr<ProcLimitsEntry> ParseProcLimitsLine(absl::string_view line) { 375 ProcLimitsEntry limits_entry = {}; 376 377 std::vector<std::string> parts = 378 absl::StrSplit(line.substr(25), ' ', absl::SkipWhitespace()); 379 380 // should have 3 parts (soft, hard, units) 381 // the name is ignored since the whole line is space separated and 382 // the name has spaces in but not a consistent number of spaces per 383 // name (e.g. 'Max cpu time' vs 'Max processes') 384 // however, since units are optional, ignore them as well 385 if (parts.size() < 2 || parts.size() > 3) { 386 return PosixError(EINVAL, absl::StrCat("Invalid line: ", line)); 387 } 388 389 // parse the limit type 390 auto limitType = line.substr(0, 25); 391 auto it = absl::c_find_if(LimitTypes, [&limitType](LimitType t) { 392 return absl::StrContains(limitType, LimitTypeToString(t)); 393 }); 394 if (it == LimitTypes.end()) { 395 return PosixError(EINVAL, absl::StrCat("Invalid limit type: ", limitType)); 396 } 397 limits_entry.limit_type = *it; 398 399 // parse soft limit 400 if (parts[0] == "unlimited") { 401 limits_entry.cur_limit = ~0ULL; 402 } else { 403 ASSIGN_OR_RETURN_ERRNO(limits_entry.cur_limit, Atoi<uint64_t>(parts[0])); 404 } 405 406 // parse hard limit 407 if (parts[1] == "unlimited") { 408 limits_entry.max_limit = ~0ULL; 409 } else { 410 ASSIGN_OR_RETURN_ERRNO(limits_entry.max_limit, Atoi<uint64_t>(parts[1])); 411 } 412 413 // ignore units 414 415 return limits_entry; 416 } 417 418 PosixErrorOr<std::vector<ProcLimitsEntry>> ParseProcLimits( 419 absl::string_view contents) { 420 std::vector<ProcLimitsEntry> entries; 421 std::vector<std::string> lines = 422 absl::StrSplit(contents, '\n', absl::SkipEmpty()); 423 // skip first line (headers) 424 for (size_t i = 1U; i < lines.size(); ++i) { 425 std::cout << "line: " << lines[i] << std::endl; 426 ASSIGN_OR_RETURN_ERRNO(auto entry, ParseProcLimitsLine(lines[i])); 427 entries.push_back(entry); 428 } 429 return entries; 430 } 431 432 std::ostream& operator<<(std::ostream& os, const ProcLimitsEntry& entry) { 433 std::string str = 434 absl::StrFormat("Max %-25s ", LimitTypeToString(entry.limit_type)); 435 436 if (entry.cur_limit == ~0ULL) { 437 absl::StrAppendFormat(&str, "%-20s ", "unlimited"); 438 } else { 439 absl::StrAppendFormat(&str, "%-20d ", entry.cur_limit); 440 } 441 442 if (entry.max_limit == ~0ULL) { 443 absl::StrAppendFormat(&str, "%-20s ", "unlimited"); 444 } else { 445 absl::StrAppendFormat(&str, "%-20d ", entry.max_limit); 446 } 447 448 switch (entry.limit_type) { 449 case LimitType::kFileSize: 450 case LimitType::kData: 451 case LimitType::kStack: 452 case LimitType::kCore: 453 case LimitType::kRSS: 454 case LimitType::kMemoryLocked: 455 case LimitType::kAS: 456 case LimitType::kMessageQueueBytes: 457 absl::StrAppendFormat(&str, "%-10s ", "bytes"); 458 break; 459 case LimitType::kCPU: 460 absl::StrAppendFormat(&str, "%-10s", "seconds"); 461 break; 462 case LimitType::kNumberOfFiles: 463 absl::StrAppendFormat(&str, "%-10s ", "files"); 464 break; 465 case LimitType::kLocks: 466 absl::StrAppendFormat(&str, "%-10s ", "locks"); 467 break; 468 case LimitType::kSignalsPending: 469 absl::StrAppendFormat(&str, "%-10s ", "signals"); 470 break; 471 case LimitType::kProcessCount: 472 absl::StrAppendFormat(&str, "%-10s ", "processes"); 473 break; 474 case LimitType::kNice: 475 absl::StrAppendFormat(&str, "%-10s ", ""); 476 break; 477 case LimitType::kRealTimePriority: 478 absl::StrAppendFormat(&str, "%-10s ", ""); 479 break; 480 case LimitType::kRttime: 481 absl::StrAppendFormat(&str, "%-10s ", "us"); 482 break; 483 } 484 485 os << str; 486 return os; 487 } 488 489 std::ostream& operator<<(std::ostream& os, 490 const std::vector<ProcLimitsEntry>& vec) { 491 os << "Limit Soft Limit Hard Limit " 492 "Units \n"; 493 for (unsigned int i = 0; i < vec.size(); i++) { 494 os << vec[i]; 495 if (i != vec.size() - 1) { 496 os << "\n"; 497 } 498 } 499 return os; 500 } 501 502 } // namespace testing 503 } // namespace gvisor