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