github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/syscalls/linux/proc_pid_smaps.cc (about) 1 // Copyright 2019 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 <stddef.h> 16 #include <stdint.h> 17 18 #include <algorithm> 19 #include <iostream> 20 #include <string> 21 #include <utility> 22 #include <vector> 23 24 #include "absl/container/flat_hash_set.h" 25 #include "absl/strings/str_cat.h" 26 #include "absl/strings/str_format.h" 27 #include "absl/strings/str_split.h" 28 #include "absl/strings/string_view.h" 29 #include "absl/types/optional.h" 30 #include "test/util/file_descriptor.h" 31 #include "test/util/fs_util.h" 32 #include "test/util/memory_util.h" 33 #include "test/util/posix_error.h" 34 #include "test/util/proc_util.h" 35 #include "test/util/temp_path.h" 36 #include "test/util/test_util.h" 37 38 using ::testing::Contains; 39 using ::testing::ElementsAreArray; 40 using ::testing::IsSupersetOf; 41 using ::testing::Not; 42 using ::testing::Optional; 43 44 namespace gvisor { 45 namespace testing { 46 47 namespace { 48 49 struct ProcPidSmapsEntry { 50 ProcMapsEntry maps_entry; 51 52 // These fields should always exist, as they were included in e070ad49f311 53 // "[PATCH] add /proc/pid/smaps". 54 size_t size_kb; 55 size_t rss_kb; 56 size_t shared_clean_kb; 57 size_t shared_dirty_kb; 58 size_t private_clean_kb; 59 size_t private_dirty_kb; 60 61 // These fields were added later and may not be present. 62 absl::optional<size_t> pss_kb; 63 absl::optional<size_t> referenced_kb; 64 absl::optional<size_t> anonymous_kb; 65 absl::optional<size_t> anon_huge_pages_kb; 66 absl::optional<size_t> shared_hugetlb_kb; 67 absl::optional<size_t> private_hugetlb_kb; 68 absl::optional<size_t> swap_kb; 69 absl::optional<size_t> swap_pss_kb; 70 absl::optional<size_t> kernel_page_size_kb; 71 absl::optional<size_t> mmu_page_size_kb; 72 absl::optional<size_t> locked_kb; 73 74 // Caution: "Note that there is no guarantee that every flag and associated 75 // mnemonic will be present in all further kernel releases. Things get 76 // changed, the flags may be vanished or the reverse -- new added." - Linux 77 // Documentation/filesystems/proc.txt, on VmFlags. Avoid checking for any 78 // flags that are not extremely well-established. 79 absl::optional<std::vector<std::string>> vm_flags; 80 }; 81 82 // Given the value part of a /proc/[pid]/smaps field containing a value in kB 83 // (for example, " 4 kB", returns the value in kB (in this example, 4). 84 PosixErrorOr<size_t> SmapsValueKb(absl::string_view value) { 85 // TODO(jamieliu): let us use RE2 or <regex> 86 std::pair<absl::string_view, absl::string_view> parts = 87 absl::StrSplit(value, ' ', absl::SkipEmpty()); 88 if (parts.second != "kB") { 89 return PosixError(EINVAL, 90 absl::StrCat("invalid smaps field value: ", value)); 91 } 92 ASSIGN_OR_RETURN_ERRNO(auto val_kb, Atoi<size_t>(parts.first)); 93 return val_kb; 94 } 95 96 PosixErrorOr<std::vector<ProcPidSmapsEntry>> ParseProcPidSmaps( 97 absl::string_view contents) { 98 std::vector<ProcPidSmapsEntry> entries; 99 absl::optional<ProcPidSmapsEntry> entry; 100 bool have_size_kb = false; 101 bool have_rss_kb = false; 102 bool have_shared_clean_kb = false; 103 bool have_shared_dirty_kb = false; 104 bool have_private_clean_kb = false; 105 bool have_private_dirty_kb = false; 106 107 auto const finish_entry = [&] { 108 if (entry) { 109 if (!have_size_kb) { 110 return PosixError(EINVAL, "smaps entry is missing Size"); 111 } 112 if (!have_rss_kb) { 113 return PosixError(EINVAL, "smaps entry is missing Rss"); 114 } 115 if (!have_shared_clean_kb) { 116 return PosixError(EINVAL, "smaps entry is missing Shared_Clean"); 117 } 118 if (!have_shared_dirty_kb) { 119 return PosixError(EINVAL, "smaps entry is missing Shared_Dirty"); 120 } 121 if (!have_private_clean_kb) { 122 return PosixError(EINVAL, "smaps entry is missing Private_Clean"); 123 } 124 if (!have_private_dirty_kb) { 125 return PosixError(EINVAL, "smaps entry is missing Private_Dirty"); 126 } 127 // std::move(entry.value()) instead of std::move(entry).value(), because 128 // otherwise tools may report a "use-after-move" warning, which is 129 // spurious because entry.emplace() below resets entry to a new 130 // ProcPidSmapsEntry. 131 entries.emplace_back(std::move(entry.value())); 132 } 133 entry.emplace(); 134 have_size_kb = false; 135 have_rss_kb = false; 136 have_shared_clean_kb = false; 137 have_shared_dirty_kb = false; 138 have_private_clean_kb = false; 139 have_private_dirty_kb = false; 140 return NoError(); 141 }; 142 143 // Holds key/value pairs from smaps field lines. Declared here so it can be 144 // captured by reference by the following lambdas. 145 std::vector<absl::string_view> key_value; 146 147 auto const on_required_field_kb = [&](size_t* field, bool* have_field) { 148 if (*have_field) { 149 return PosixError( 150 EINVAL, 151 absl::StrFormat("smaps entry has duplicate %s line", key_value[0])); 152 } 153 ASSIGN_OR_RETURN_ERRNO(*field, SmapsValueKb(key_value[1])); 154 *have_field = true; 155 return NoError(); 156 }; 157 158 auto const on_optional_field_kb = [&](absl::optional<size_t>* field) { 159 if (*field) { 160 return PosixError( 161 EINVAL, 162 absl::StrFormat("smaps entry has duplicate %s line", key_value[0])); 163 } 164 ASSIGN_OR_RETURN_ERRNO(*field, SmapsValueKb(key_value[1])); 165 return NoError(); 166 }; 167 168 absl::flat_hash_set<std::string> unknown_fields; 169 auto const on_unknown_field = [&] { 170 absl::string_view key = key_value[0]; 171 // Don't mention unknown fields more than once. 172 if (unknown_fields.count(key)) { 173 return; 174 } 175 unknown_fields.insert(std::string(key)); 176 std::cerr << "skipping unknown smaps field " << key << std::endl; 177 }; 178 179 auto lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); 180 for (absl::string_view l : lines) { 181 // Is this line a valid /proc/[pid]/maps entry? 182 auto maybe_maps_entry = ParseProcMapsLine(l); 183 if (maybe_maps_entry.ok()) { 184 // This marks the beginning of a new /proc/[pid]/smaps entry. 185 RETURN_IF_ERRNO(finish_entry()); 186 entry->maps_entry = std::move(maybe_maps_entry).ValueOrDie(); 187 continue; 188 } 189 // Otherwise it's a field in an existing /proc/[pid]/smaps entry of the form 190 // "key:value" (where value in practice will be preceded by a variable 191 // amount of whitespace). 192 if (!entry) { 193 std::cerr << "smaps line not considered a maps line: " 194 << maybe_maps_entry.error().message() << std::endl; 195 return PosixError( 196 EINVAL, 197 absl::StrCat("smaps field line without preceding maps line: ", l)); 198 } 199 key_value = absl::StrSplit(l, absl::MaxSplits(':', 1)); 200 if (key_value.size() != 2) { 201 return PosixError(EINVAL, absl::StrCat("invalid smaps field line: ", l)); 202 } 203 absl::string_view const key = key_value[0]; 204 if (key == "Size") { 205 RETURN_IF_ERRNO(on_required_field_kb(&entry->size_kb, &have_size_kb)); 206 } else if (key == "Rss") { 207 RETURN_IF_ERRNO(on_required_field_kb(&entry->rss_kb, &have_rss_kb)); 208 } else if (key == "Shared_Clean") { 209 RETURN_IF_ERRNO( 210 on_required_field_kb(&entry->shared_clean_kb, &have_shared_clean_kb)); 211 } else if (key == "Shared_Dirty") { 212 RETURN_IF_ERRNO( 213 on_required_field_kb(&entry->shared_dirty_kb, &have_shared_dirty_kb)); 214 } else if (key == "Private_Clean") { 215 RETURN_IF_ERRNO(on_required_field_kb(&entry->private_clean_kb, 216 &have_private_clean_kb)); 217 } else if (key == "Private_Dirty") { 218 RETURN_IF_ERRNO(on_required_field_kb(&entry->private_dirty_kb, 219 &have_private_dirty_kb)); 220 } else if (key == "Pss") { 221 RETURN_IF_ERRNO(on_optional_field_kb(&entry->pss_kb)); 222 } else if (key == "Referenced") { 223 RETURN_IF_ERRNO(on_optional_field_kb(&entry->referenced_kb)); 224 } else if (key == "Anonymous") { 225 RETURN_IF_ERRNO(on_optional_field_kb(&entry->anonymous_kb)); 226 } else if (key == "AnonHugePages") { 227 RETURN_IF_ERRNO(on_optional_field_kb(&entry->anon_huge_pages_kb)); 228 } else if (key == "Shared_Hugetlb") { 229 RETURN_IF_ERRNO(on_optional_field_kb(&entry->shared_hugetlb_kb)); 230 } else if (key == "Private_Hugetlb") { 231 RETURN_IF_ERRNO(on_optional_field_kb(&entry->private_hugetlb_kb)); 232 } else if (key == "Swap") { 233 RETURN_IF_ERRNO(on_optional_field_kb(&entry->swap_kb)); 234 } else if (key == "SwapPss") { 235 RETURN_IF_ERRNO(on_optional_field_kb(&entry->swap_pss_kb)); 236 } else if (key == "KernelPageSize") { 237 RETURN_IF_ERRNO(on_optional_field_kb(&entry->kernel_page_size_kb)); 238 } else if (key == "MMUPageSize") { 239 RETURN_IF_ERRNO(on_optional_field_kb(&entry->mmu_page_size_kb)); 240 } else if (key == "Locked") { 241 RETURN_IF_ERRNO(on_optional_field_kb(&entry->locked_kb)); 242 } else if (key == "VmFlags") { 243 if (entry->vm_flags) { 244 return PosixError(EINVAL, "duplicate VmFlags line"); 245 } 246 entry->vm_flags = absl::StrSplit(key_value[1], ' ', absl::SkipEmpty()); 247 } else { 248 on_unknown_field(); 249 } 250 } 251 RETURN_IF_ERRNO(finish_entry()); 252 return entries; 253 }; 254 255 TEST(ParseProcPidSmapsTest, Correctness) { 256 auto entries = ASSERT_NO_ERRNO_AND_VALUE( 257 ParseProcPidSmaps("0-10000 rw-s 00000000 00:00 0 " 258 " /dev/zero (deleted)\n" 259 "Size: 0 kB\n" 260 "Rss: 1 kB\n" 261 "Pss: 2 kB\n" 262 "Shared_Clean: 3 kB\n" 263 "Shared_Dirty: 4 kB\n" 264 "Private_Clean: 5 kB\n" 265 "Private_Dirty: 6 kB\n" 266 "Referenced: 7 kB\n" 267 "Anonymous: 8 kB\n" 268 "AnonHugePages: 9 kB\n" 269 "Shared_Hugetlb: 10 kB\n" 270 "Private_Hugetlb: 11 kB\n" 271 "Swap: 12 kB\n" 272 "SwapPss: 13 kB\n" 273 "KernelPageSize: 14 kB\n" 274 "MMUPageSize: 15 kB\n" 275 "Locked: 16 kB\n" 276 "FutureUnknownKey: 17 kB\n" 277 "VmFlags: rd wr sh mr mw me ms lo ?? sd \n")); 278 ASSERT_EQ(entries.size(), 1); 279 auto& entry = entries[0]; 280 EXPECT_EQ(entry.maps_entry.filename, "/dev/zero (deleted)"); 281 EXPECT_EQ(entry.size_kb, 0); 282 EXPECT_EQ(entry.rss_kb, 1); 283 EXPECT_THAT(entry.pss_kb, Optional(2)); 284 EXPECT_EQ(entry.shared_clean_kb, 3); 285 EXPECT_EQ(entry.shared_dirty_kb, 4); 286 EXPECT_EQ(entry.private_clean_kb, 5); 287 EXPECT_EQ(entry.private_dirty_kb, 6); 288 EXPECT_THAT(entry.referenced_kb, Optional(7)); 289 EXPECT_THAT(entry.anonymous_kb, Optional(8)); 290 EXPECT_THAT(entry.anon_huge_pages_kb, Optional(9)); 291 EXPECT_THAT(entry.shared_hugetlb_kb, Optional(10)); 292 EXPECT_THAT(entry.private_hugetlb_kb, Optional(11)); 293 EXPECT_THAT(entry.swap_kb, Optional(12)); 294 EXPECT_THAT(entry.swap_pss_kb, Optional(13)); 295 EXPECT_THAT(entry.kernel_page_size_kb, Optional(14)); 296 EXPECT_THAT(entry.mmu_page_size_kb, Optional(15)); 297 EXPECT_THAT(entry.locked_kb, Optional(16)); 298 EXPECT_THAT(entry.vm_flags, 299 Optional(ElementsAreArray({"rd", "wr", "sh", "mr", "mw", "me", 300 "ms", "lo", "??", "sd"}))); 301 } 302 303 // Returns the unique entry in entries containing the given address. 304 PosixErrorOr<ProcPidSmapsEntry> FindUniqueSmapsEntry( 305 std::vector<ProcPidSmapsEntry> const& entries, uintptr_t addr) { 306 auto const pred = [&](ProcPidSmapsEntry const& entry) { 307 return entry.maps_entry.start <= addr && addr < entry.maps_entry.end; 308 }; 309 auto const it = std::find_if(entries.begin(), entries.end(), pred); 310 if (it == entries.end()) { 311 return PosixError(EINVAL, 312 absl::StrFormat("no entry contains address %#x", addr)); 313 } 314 auto const it2 = std::find_if(it + 1, entries.end(), pred); 315 if (it2 != entries.end()) { 316 return PosixError( 317 EINVAL, 318 absl::StrFormat("overlapping entries [%#x-%#x) and [%#x-%#x) both " 319 "contain address %#x", 320 it->maps_entry.start, it->maps_entry.end, 321 it2->maps_entry.start, it2->maps_entry.end, addr)); 322 } 323 return *it; 324 } 325 326 PosixErrorOr<std::vector<ProcPidSmapsEntry>> ReadProcSelfSmaps() { 327 ASSIGN_OR_RETURN_ERRNO(std::string contents, GetContents("/proc/self/smaps")); 328 return ParseProcPidSmaps(contents); 329 } 330 331 TEST(ProcPidSmapsTest, SharedAnon) { 332 // Map with MAP_POPULATE so we get some RSS. 333 Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon( 334 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE)); 335 auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); 336 auto const entry = 337 ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); 338 339 EXPECT_EQ(entry.size_kb, m.len() / 1024); 340 // It's possible that populated pages have been swapped out, so RSS might be 341 // less than size. 342 EXPECT_LE(entry.rss_kb, entry.size_kb); 343 344 if (entry.pss_kb) { 345 // PSS should be exactly equal to RSS since no other address spaces should 346 // be sharing our new mapping. 347 EXPECT_EQ(entry.pss_kb.value(), entry.rss_kb); 348 } 349 350 // "Shared" and "private" in smaps refers to whether or not *physical pages* 351 // are shared; thus all pages in our MAP_SHARED mapping should nevertheless 352 // be private. 353 EXPECT_EQ(entry.shared_clean_kb, 0); 354 EXPECT_EQ(entry.shared_dirty_kb, 0); 355 EXPECT_EQ(entry.private_clean_kb + entry.private_dirty_kb, entry.rss_kb) 356 << "Private_Clean = " << entry.private_clean_kb 357 << " kB, Private_Dirty = " << entry.private_dirty_kb << " kB"; 358 359 // Shared anonymous mappings are implemented as a shmem file, so their pages 360 // are not PageAnon. 361 if (entry.anonymous_kb) { 362 EXPECT_EQ(entry.anonymous_kb.value(), 0); 363 } 364 365 if (entry.vm_flags) { 366 EXPECT_THAT(entry.vm_flags.value(), 367 IsSupersetOf({"rd", "wr", "sh", "mr", "mw", "me", "ms"})); 368 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); 369 } 370 } 371 372 TEST(ProcPidSmapsTest, PrivateAnon) { 373 // Map with MAP_POPULATE so we get some RSS. 374 Mapping const m = ASSERT_NO_ERRNO_AND_VALUE( 375 MmapAnon(2 * kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_POPULATE)); 376 auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); 377 auto const entry = 378 ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); 379 380 // It's possible that our mapping was merged with another vma, so the smaps 381 // entry might be bigger than our original mapping. 382 EXPECT_GE(entry.size_kb, m.len() / 1024); 383 EXPECT_LE(entry.rss_kb, entry.size_kb); 384 if (entry.pss_kb) { 385 EXPECT_LE(entry.pss_kb.value(), entry.rss_kb); 386 } 387 388 if (entry.anonymous_kb) { 389 EXPECT_EQ(entry.anonymous_kb.value(), entry.rss_kb); 390 } 391 392 if (entry.vm_flags) { 393 EXPECT_THAT(entry.vm_flags.value(), IsSupersetOf({"wr", "mr", "mw", "me"})); 394 // We passed PROT_WRITE to mmap. On at least x86, the mapping is in 395 // practice readable because there is no way to configure the MMU to make 396 // pages writable but not readable. However, VmFlags should reflect the 397 // flags set on the VMA, so "rd" (VM_READ) should not appear in VmFlags. 398 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("rd"))); 399 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); 400 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("sh"))); 401 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ms"))); 402 } 403 } 404 405 TEST(ProcPidSmapsTest, SharedReadOnlyFile) { 406 size_t const kFileSize = kPageSize; 407 408 auto const temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); 409 ASSERT_THAT(truncate(temp_file.path().c_str(), kFileSize), SyscallSucceeds()); 410 auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY)); 411 412 auto const m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( 413 nullptr, kFileSize, PROT_READ, MAP_SHARED | MAP_POPULATE, fd.get(), 0)); 414 auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); 415 auto const entry = 416 ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); 417 418 // Most of the same logic as the SharedAnon case applies. 419 EXPECT_EQ(entry.size_kb, kFileSize / 1024); 420 EXPECT_LE(entry.rss_kb, entry.size_kb); 421 if (entry.pss_kb) { 422 EXPECT_EQ(entry.pss_kb.value(), entry.rss_kb); 423 } 424 EXPECT_EQ(entry.shared_clean_kb, 0); 425 EXPECT_EQ(entry.shared_dirty_kb, 0); 426 EXPECT_EQ(entry.private_clean_kb + entry.private_dirty_kb, entry.rss_kb) 427 << "Private_Clean = " << entry.private_clean_kb 428 << " kB, Private_Dirty = " << entry.private_dirty_kb << " kB"; 429 if (entry.anonymous_kb) { 430 EXPECT_EQ(entry.anonymous_kb.value(), 0); 431 } 432 433 if (entry.vm_flags) { 434 EXPECT_THAT(entry.vm_flags.value(), IsSupersetOf({"rd", "mr", "me", "ms"})); 435 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("wr"))); 436 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); 437 // Because the mapped file was opened O_RDONLY, the VMA is !VM_MAYWRITE and 438 // also !VM_SHARED. 439 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("sh"))); 440 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("mw"))); 441 } 442 } 443 444 // Tests that gVisor's /proc/[pid]/smaps provides all of the fields we expect it 445 // to, which as of this writing is all fields provided by Linux 4.4. 446 TEST(ProcPidSmapsTest, GvisorFields) { 447 SKIP_IF(!IsRunningOnGvisor()); 448 auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); 449 for (auto const& entry : entries) { 450 EXPECT_TRUE(entry.pss_kb); 451 EXPECT_TRUE(entry.referenced_kb); 452 EXPECT_TRUE(entry.anonymous_kb); 453 EXPECT_TRUE(entry.anon_huge_pages_kb); 454 EXPECT_TRUE(entry.shared_hugetlb_kb); 455 EXPECT_TRUE(entry.private_hugetlb_kb); 456 EXPECT_TRUE(entry.swap_kb); 457 EXPECT_TRUE(entry.swap_pss_kb); 458 EXPECT_THAT(entry.kernel_page_size_kb, Optional(kPageSize / 1024)); 459 EXPECT_THAT(entry.mmu_page_size_kb, Optional(kPageSize / 1024)); 460 EXPECT_TRUE(entry.locked_kb); 461 EXPECT_TRUE(entry.vm_flags); 462 } 463 } 464 465 } // namespace 466 467 } // namespace testing 468 } // namespace gvisor