910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/memory/partition_allocator/inspect_utils.h"

#include <sys/mman.h>

#include "base/check_op.h"
#include "base/debug/proc_maps_linux.h"
#include "base/logging.h"
#include "base/memory/page_size.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/stringprintf.h"
#include "partition_alloc/thread_cache.h"

namespace partition_alloc::tools {

base::ScopedFD OpenProcMem(pid_t pid) {
  std::string path = base::StringPrintf("/proc/%d/mem", pid);
  int fd = open(path.c_str(), O_RDONLY);
  CHECK_NE(fd, -1)
      << "Do you have 0 set in /proc/sys/kernel/yama/ptrace_scope?";

  return base::ScopedFD(fd);
}

RemoteProcessMemoryReader::RemoteProcessMemoryReader(pid_t pid) : pid_(pid) {
  mem_fd_ = OpenProcMem(pid_);
  is_valid_ = mem_fd_.get() != -1;
}

bool RemoteProcessMemoryReader::ReadMemory(uintptr_t remote_address,
                                           size_t size,
                                           char* buffer) {
  if (HANDLE_EINTR(pread(mem_fd_.get(), buffer, size,
                         static_cast<off_t>(remote_address))) ==
      static_cast<ssize_t>(size)) {
    return true;
  }
  return false;
}

base::ScopedFD OpenPagemap(pid_t pid) {
  std::string path = base::StringPrintf("/proc/%d/pagemap", pid);
  int fd = open(path.c_str(), O_RDONLY);
  CHECK_NE(fd, -1)
      << "Do you have 0 set in /proc/sys/kernel/yama/ptrace_scope?";

  return base::ScopedFD(fd);
}

uintptr_t IndexThreadCacheNeedleArray(RemoteProcessMemoryReader& reader,
                                      size_t index) {
  std::vector<base::debug::MappedMemoryRegion> regions;
  DCHECK_LT(index, kThreadCacheNeedleArraySize);

  {
    // Ensures that the mappings are not going to change.
    ScopedSigStopper stop{reader.pid()};

    // There are subtleties when trying to read this file, which we blissfully
    // ignore here. See //base/debug/proc_maps_linux.h for details. We don't use
    // it, since we don't read the maps for ourselves, and everything is already
    // extremely racy. At worst we have to retry.
    LOG(INFO) << "Opening /proc/PID/maps";
    std::string path = base::StringPrintf("/proc/%d/maps", reader.pid());
    auto file = base::File(base::FilePath(path),
                           base::File::FLAG_OPEN | base::File::FLAG_READ);
    CHECK(file.IsValid());
    constexpr size_t kMaxFileSize = 10 * (1 << 20);
    std::vector<char> data(kMaxFileSize);
    int bytes_read =
        file.ReadAtCurrentPos(&data[0], static_cast<int>(data.size()) - 1);
    CHECK_GT(bytes_read, 0) << "Cannot read " << path;
    data[bytes_read] = '\0';
    std::string proc_maps(&data[0]);

    LOG(INFO) << "Parsing the maps";
    CHECK(base::debug::ParseProcMaps(proc_maps, &regions));
    LOG(INFO) << "Found " << regions.size() << " regions";
  }

  for (auto& region : regions) {
    using base::debug::MappedMemoryRegion;

    // The array is in .data, meaning that it's mapped from the executable, and
    // has rw-p permissions. For Chrome, .data is quite small, hence the size
    // limit.
    uint8_t expected_permissions = MappedMemoryRegion::Permission::READ |
                                   MappedMemoryRegion::Permission::WRITE |
                                   MappedMemoryRegion::Permission::PRIVATE;
    size_t region_size = region.end - region.start;
    constexpr size_t kMaxRegionSize = 10 * (1 << 20);
    if (region.permissions != expected_permissions ||
        region_size > kMaxRegionSize || region.path.empty()) {
      continue;
    }

    LOG(INFO) << "Found a candidate region between " << std::hex << region.start
              << " and " << region.end << std::dec
              << " (size = " << region.end - region.start
              << ") path = " << region.path;
    // Scan the region, looking for the needles.
    uintptr_t needle_array_candidate[kThreadCacheNeedleArraySize];
    for (uintptr_t address = region.start;
         address < region.end - sizeof(needle_array_candidate);
         address += sizeof(uintptr_t)) {
      bool ok =
          reader.ReadMemory(reinterpret_cast<unsigned long>(address),
                            sizeof(needle_array_candidate),
                            reinterpret_cast<char*>(needle_array_candidate));
      if (!ok) {
        LOG(WARNING) << "Failed to read";
        continue;
      }

      if (needle_array_candidate[0] == kNeedle1 &&
          needle_array_candidate[kThreadCacheNeedleArraySize - 1] == kNeedle2) {
        LOG(INFO) << "Got it! Address = 0x" << std::hex
                  << needle_array_candidate[index];
        return needle_array_candidate[index];
      }
    }
  }

  LOG(ERROR) << "Failed to find the address";
  return 0;
}

}  // namespace partition_alloc::tools