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