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