gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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 "test/util/file_descriptor.h" 30 #include "test/util/fs_util.h" 31 #include "test/util/memory_util.h" 32 #include "test/util/posix_error.h" 33 #include "test/util/proc_util.h" 34 #include "test/util/temp_path.h" 35 #include "test/util/test_util.h" 36 37 using ::testing::Contains; 38 using ::testing::ElementsAreArray; 39 using ::testing::IsSupersetOf; 40 using ::testing::Not; 41 using ::testing::Optional; 42 43 namespace gvisor { 44 namespace testing { 45 46 namespace { 47 TEST(ProcPidSmapsTest, SharedAnon) { 48 // Map with MAP_POPULATE so we get some RSS. 49 Mapping const m = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon( 50 2 * kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE)); 51 auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); 52 auto const entry = 53 ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); 54 55 EXPECT_EQ(entry.size_kb, m.len() / 1024); 56 // It's possible that populated pages have been swapped out, so RSS might be 57 // less than size. 58 EXPECT_LE(entry.rss_kb, entry.size_kb); 59 60 if (entry.pss_kb) { 61 // PSS should be exactly equal to RSS since no other address spaces should 62 // be sharing our new mapping. 63 EXPECT_EQ(entry.pss_kb.value(), entry.rss_kb); 64 } 65 66 // "Shared" and "private" in smaps refers to whether or not *physical pages* 67 // are shared; thus all pages in our MAP_SHARED mapping should nevertheless 68 // be private. 69 EXPECT_EQ(entry.shared_clean_kb, 0); 70 EXPECT_EQ(entry.shared_dirty_kb, 0); 71 EXPECT_EQ(entry.private_clean_kb + entry.private_dirty_kb, entry.rss_kb) 72 << "Private_Clean = " << entry.private_clean_kb 73 << " kB, Private_Dirty = " << entry.private_dirty_kb << " kB"; 74 75 // Shared anonymous mappings are implemented as a shmem file, so their pages 76 // are not PageAnon. 77 if (entry.anonymous_kb) { 78 EXPECT_EQ(entry.anonymous_kb.value(), 0); 79 } 80 81 if (entry.vm_flags) { 82 EXPECT_THAT(entry.vm_flags.value(), 83 IsSupersetOf({"rd", "wr", "sh", "mr", "mw", "me", "ms"})); 84 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); 85 } 86 } 87 88 TEST(ProcPidSmapsTest, PrivateAnon) { 89 // Map with MAP_POPULATE so we get some RSS. 90 Mapping const m = ASSERT_NO_ERRNO_AND_VALUE( 91 MmapAnon(2 * kPageSize, PROT_WRITE, MAP_PRIVATE | MAP_POPULATE)); 92 auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); 93 auto const entry = 94 ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); 95 96 // It's possible that our mapping was merged with another vma, so the smaps 97 // entry might be bigger than our original mapping. 98 EXPECT_GE(entry.size_kb, m.len() / 1024); 99 EXPECT_LE(entry.rss_kb, entry.size_kb); 100 if (entry.pss_kb) { 101 EXPECT_LE(entry.pss_kb.value(), entry.rss_kb); 102 } 103 104 if (entry.anonymous_kb) { 105 EXPECT_EQ(entry.anonymous_kb.value(), entry.rss_kb); 106 } 107 108 if (entry.vm_flags) { 109 EXPECT_THAT(entry.vm_flags.value(), IsSupersetOf({"wr", "mr", "mw", "me"})); 110 // We passed PROT_WRITE to mmap. On at least x86, the mapping is in 111 // practice readable because there is no way to configure the MMU to make 112 // pages writable but not readable. However, VmFlags should reflect the 113 // flags set on the VMA, so "rd" (VM_READ) should not appear in VmFlags. 114 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("rd"))); 115 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); 116 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("sh"))); 117 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ms"))); 118 } 119 } 120 121 TEST(ProcPidSmapsTest, SharedReadOnlyFile) { 122 size_t const kFileSize = kPageSize; 123 124 auto const temp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); 125 ASSERT_THAT(truncate(temp_file.path().c_str(), kFileSize), SyscallSucceeds()); 126 auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(temp_file.path(), O_RDONLY)); 127 128 auto const m = ASSERT_NO_ERRNO_AND_VALUE(Mmap( 129 nullptr, kFileSize, PROT_READ, MAP_SHARED | MAP_POPULATE, fd.get(), 0)); 130 auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); 131 auto const entry = 132 ASSERT_NO_ERRNO_AND_VALUE(FindUniqueSmapsEntry(entries, m.addr())); 133 134 // Most of the same logic as the SharedAnon case applies. 135 EXPECT_EQ(entry.size_kb, kFileSize / 1024); 136 EXPECT_LE(entry.rss_kb, entry.size_kb); 137 if (entry.pss_kb) { 138 EXPECT_EQ(entry.pss_kb.value(), entry.rss_kb); 139 } 140 EXPECT_EQ(entry.shared_clean_kb, 0); 141 EXPECT_EQ(entry.shared_dirty_kb, 0); 142 EXPECT_EQ(entry.private_clean_kb + entry.private_dirty_kb, entry.rss_kb) 143 << "Private_Clean = " << entry.private_clean_kb 144 << " kB, Private_Dirty = " << entry.private_dirty_kb << " kB"; 145 if (entry.anonymous_kb) { 146 EXPECT_EQ(entry.anonymous_kb.value(), 0); 147 } 148 149 if (entry.vm_flags) { 150 EXPECT_THAT(entry.vm_flags.value(), IsSupersetOf({"rd", "mr", "me", "ms"})); 151 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("wr"))); 152 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("ex"))); 153 // Because the mapped file was opened O_RDONLY, the VMA is !VM_MAYWRITE and 154 // also !VM_SHARED. 155 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("sh"))); 156 EXPECT_THAT(entry.vm_flags.value(), Not(Contains("mw"))); 157 } 158 } 159 160 // Tests that gVisor's /proc/[pid]/smaps provides all of the fields we expect it 161 // to, which as of this writing is all fields provided by Linux 4.4. 162 TEST(ProcPidSmapsTest, GvisorFields) { 163 SKIP_IF(!IsRunningOnGvisor()); 164 auto const entries = ASSERT_NO_ERRNO_AND_VALUE(ReadProcSelfSmaps()); 165 for (auto const& entry : entries) { 166 EXPECT_TRUE(entry.pss_kb); 167 EXPECT_TRUE(entry.referenced_kb); 168 EXPECT_TRUE(entry.anonymous_kb); 169 EXPECT_TRUE(entry.anon_huge_pages_kb); 170 EXPECT_TRUE(entry.shared_hugetlb_kb); 171 EXPECT_TRUE(entry.private_hugetlb_kb); 172 EXPECT_TRUE(entry.swap_kb); 173 EXPECT_TRUE(entry.swap_pss_kb); 174 EXPECT_THAT(entry.kernel_page_size_kb, Optional(kPageSize / 1024)); 175 EXPECT_THAT(entry.mmu_page_size_kb, Optional(kPageSize / 1024)); 176 EXPECT_TRUE(entry.locked_kb); 177 EXPECT_TRUE(entry.vm_flags); 178 } 179 } 180 181 } // namespace 182 183 } // namespace testing 184 } // namespace gvisor