Commit d3d0bbfc authored by Brian Geffon's avatar Brian Geffon Committed by Commit Bot

CrOS Memory: Add a pagemap parser

The pagemap is a file in procfs for each process. It contains
information on every page mapped into that processes virtual
address space. This CL adds a pagemap parser as we will need
one for future CrOS projects, specifically userspace swap.

Bug: 1067833
Change-Id: I31aafcebd5894899f13748d6a3f13f9f1edf375d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2138771
Commit-Queue: Brian Geffon <bgeffon@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757199}
parent 68f90ed1
...@@ -56,6 +56,8 @@ component("chromeos") { ...@@ -56,6 +56,8 @@ component("chromeos") {
"memory/kstaled.h", "memory/kstaled.h",
"memory/memory.cc", "memory/memory.cc",
"memory/memory.h", "memory/memory.h",
"memory/pagemap.cc",
"memory/pagemap.h",
"memory/swap_configuration.cc", "memory/swap_configuration.cc",
"memory/swap_configuration.h", "memory/swap_configuration.h",
"policy/weekly_time/time_utils.cc", "policy/weekly_time/time_utils.cc",
...@@ -192,6 +194,7 @@ test("chromeos_unittests") { ...@@ -192,6 +194,7 @@ test("chromeos_unittests") {
"//url", "//url",
] ]
sources = [ sources = [
"memory/pagemap_unittest.cc",
"policy/weekly_time/time_utils_unittest.cc", "policy/weekly_time/time_utils_unittest.cc",
"policy/weekly_time/weekly_time_interval_unittest.cc", "policy/weekly_time/weekly_time_interval_unittest.cc",
"policy/weekly_time/weekly_time_unittest.cc", "policy/weekly_time/weekly_time_unittest.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/memory/pagemap.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include "base/memory/aligned_memory.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/process_metrics.h"
#include "base/strings/stringprintf.h"
#include "base/threading/scoped_blocking_call.h"
namespace chromeos {
namespace memory {
namespace {
constexpr char kPagemapFileFormat[] = "/proc/%d/pagemap";
}
Pagemap::~Pagemap() = default;
Pagemap::Pagemap(pid_t pid) {
if (pid) {
std::string pagemap_file = base::StringPrintf(kPagemapFileFormat, pid);
fd_.reset(HANDLE_EINTR(open(pagemap_file.c_str(), O_RDONLY)));
}
}
bool Pagemap::IsValid() const {
return fd_.is_valid();
}
bool Pagemap::GetEntries(uint64_t address,
uint64_t length,
std::vector<PagemapEntry>* entries) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
DCHECK(IsValid());
CHECK(entries);
const size_t kPageSize = base::GetPageSize();
CHECK(base::IsPageAligned(address));
CHECK(base::IsPageAligned(length));
// The size of each pagemap entry to calculate our offset in the file.
uint64_t num_pages = length / kPageSize;
if (entries->size() != num_pages) {
// Shrink or grow entries to the correct length if it was not already.
entries->resize(num_pages);
entries->shrink_to_fit(); // If we made it smaller shrink capacity.
}
uint64_t pagemap_offset = (address / kPageSize) * sizeof(PagemapEntry);
uint64_t pagemap_len = num_pages * sizeof(PagemapEntry);
memset(entries->data(), 0, pagemap_len);
// The caller was expected to provide a buffer large enough for the number of
// pages in the region.
uint64_t total_read = 0;
while (total_read < pagemap_len) {
ssize_t bytes_read = HANDLE_EINTR(
pread(fd_.get(), reinterpret_cast<char*>(entries->data()) + total_read,
pagemap_len - total_read, pagemap_offset + total_read));
if (bytes_read <= 0) {
return false;
}
total_read += bytes_read;
}
return true;
}
} // namespace memory
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_MEMORY_PAGEMAP_H_
#define CHROMEOS_MEMORY_PAGEMAP_H_
#include <sys/types.h>
#include <unistd.h>
#include <vector>
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "chromeos/chromeos_export.h"
namespace chromeos {
namespace memory {
// Pagemap fetches pagemap entries from procfs for a process.
class CHROMEOS_EXPORT Pagemap {
public:
// For more information on the Pagemap layout see the kernel documentation at
// https://www.kernel.org/doc/Documentation/vm/pagemap.txt
struct PagemapEntry {
uint8_t swap_type : 5;
uint64_t swap_offset : 50;
bool pte_soft_dirty : 1;
bool page_exclusively_mapped : 1;
uint8_t : 4; // these bits are unused
bool page_file_or_shared_anon : 1;
bool page_swapped : 1;
bool page_present : 1;
} __attribute__((packed));
static_assert(sizeof(PagemapEntry) == sizeof(uint64_t),
"PagemapEntry is expected to be 8 bytes");
explicit Pagemap(pid_t pid);
~Pagemap();
bool IsValid() const;
// GetEntries will populate |entries| for the memory region specified.
// It is required that |address| be page aligned and |length| must always be a
// page length multiple. Additionally, |entries| is expected to be sized to
// the number of pages in the range specified. If it's not properly sized it
// will be resized to the appropriate length for the caller.
bool GetEntries(uint64_t address,
uint64_t length,
std::vector<PagemapEntry>* entries);
private:
friend class PagemapTest;
base::ScopedFD fd_;
DISALLOW_COPY_AND_ASSIGN(Pagemap);
};
} // namespace memory
} // namespace chromeos
#endif // CHROMEOS_MEMORY_PAGEMAP_H_
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/memory/pagemap.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <chrono>
#include <random>
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/process_metrics.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace memory {
namespace {
template <typename T, size_t N>
constexpr size_t countof(T (&array)[N]) {
return N;
}
} // namespace
class PagemapTest : public testing::Test {
public:
void SetUp() override {
// Use a memfd so we can truncate it to arbitrary lengths to simulate a real
// pagemap file in procfs.
pagemap_.fd_.reset(memfd_create("pagemap_test", MFD_CLOEXEC));
ASSERT_TRUE(pagemap_.fd_.is_valid());
}
void TearDown() override {}
protected:
// Set the size of the pagemap appropriate for this many pages.
void CreateStorageForPages(uint64_t pages,
void** start_address,
void** end_address) {
ASSERT_NE(
ftruncate(pagemap_.fd_.get(), pages * sizeof(Pagemap::PagemapEntry)),
-1);
*start_address = 0x0;
*end_address =
reinterpret_cast<char*>(*start_address) + base::GetPageSize() * pages;
}
void PutEntries(void* address, uint64_t* entries, size_t size) {
ASSERT_EQ(HANDLE_EINTR(pwrite(
pagemap_.fd_.get(), entries, sizeof(entries[0]) * size,
reinterpret_cast<off_t>(address) / base::GetPageSize())),
static_cast<ssize_t>(sizeof(entries[0]) * size));
}
Pagemap pagemap_{0};
};
// See: https://www.kernel.org/doc/Documentation/vm/pagemap.txt for more
// information.
//
// We make sure that we only have 55 bits set of the PFN which is why we and it
// with 2^55 - 1.
#define PFN(VALUE) \
((reinterpret_cast<uint64_t>(reinterpret_cast<void*>(VALUE))) & \
((static_cast<uint64_t>(1) << 55) - 1))
#define SOFT_DIRTY static_cast<uint64_t>(1) << 55
#define EXCLUSIVELY_MAPPED static_cast<uint64_t>(1) << 56
#define FILE_PAGE static_cast<uint64_t>(1) << 61
#define SWAPPED static_cast<uint64_t>(1) << 62
#define PRESENT static_cast<uint64_t>(1) << 63
#define PFN_FROM_TYPE_AND_OFFSET(SWAP_TYPE, OFFSET) \
((SWAP_TYPE & 0x1F) | (OFFSET << 5))
TEST_F(PagemapTest, Basic) {
void* start_address;
void* end_address;
CreateStorageForPages(4, &start_address, &end_address);
uint64_t page_map_entries[] = {PFN(8675309) | SOFT_DIRTY | PRESENT,
PFN(1234) | SWAPPED,
PFN(5551212) | SOFT_DIRTY | FILE_PAGE,
PFN(0xF00) | PRESENT | EXCLUSIVELY_MAPPED};
PutEntries(start_address, page_map_entries, countof(page_map_entries));
// We now have 4 pages populated in the page map from start address to 4 *
// pagesize();
// Validate that the Pagemap class can interpret them correctly.
std::vector<Pagemap::PagemapEntry> entries;
entries.resize(countof(page_map_entries));
ASSERT_TRUE(pagemap_.GetEntries(
reinterpret_cast<uint64_t>(start_address),
countof(page_map_entries) * base::GetPageSize(), &entries));
ASSERT_EQ(
PFN_FROM_TYPE_AND_OFFSET(entries[0].swap_type, entries[0].swap_offset),
8675309u);
ASSERT_EQ(entries[0].pte_soft_dirty, true);
ASSERT_EQ(entries[0].page_present, true);
ASSERT_EQ(entries[0].page_swapped, false);
ASSERT_EQ(entries[0].page_exclusively_mapped, false);
ASSERT_EQ(entries[0].page_file_or_shared_anon, false);
ASSERT_EQ(
PFN_FROM_TYPE_AND_OFFSET(entries[1].swap_type, entries[1].swap_offset),
1234u);
ASSERT_EQ(entries[1].pte_soft_dirty, false);
ASSERT_EQ(entries[1].page_present, false);
ASSERT_EQ(entries[1].page_swapped, true);
ASSERT_EQ(entries[1].page_exclusively_mapped, false);
ASSERT_EQ(entries[1].page_file_or_shared_anon, false);
ASSERT_EQ(
PFN_FROM_TYPE_AND_OFFSET(entries[2].swap_type, entries[2].swap_offset),
5551212u);
ASSERT_EQ(entries[2].pte_soft_dirty, true);
ASSERT_EQ(entries[2].page_present, false);
ASSERT_EQ(entries[2].page_swapped, false);
ASSERT_EQ(entries[2].page_exclusively_mapped, false);
ASSERT_EQ(entries[2].page_file_or_shared_anon, true);
ASSERT_EQ(
PFN_FROM_TYPE_AND_OFFSET(entries[3].swap_type, entries[3].swap_offset),
0xF00u);
ASSERT_EQ(entries[3].pte_soft_dirty, false);
ASSERT_EQ(entries[3].page_present, true);
ASSERT_EQ(entries[3].page_swapped, false);
ASSERT_EQ(entries[3].page_exclusively_mapped, true);
ASSERT_EQ(entries[3].page_file_or_shared_anon, false);
}
TEST_F(PagemapTest, MidRangeRead) {
// This test will validate that we can read only a portion of the pages.
void* start_address;
void* end_address;
CreateStorageForPages(4, &start_address, &end_address);
uint64_t page_map_entries[] = {PFN(8675309) | SOFT_DIRTY | PRESENT,
PFN(1234) | SWAPPED,
PFN(5551212) | SOFT_DIRTY | FILE_PAGE,
PFN(0xF00) | PRESENT | EXCLUSIVELY_MAPPED};
PutEntries(start_address, page_map_entries, countof(page_map_entries));
// We will read the two middle pages.
std::vector<Pagemap::PagemapEntry> entries;
ASSERT_TRUE(pagemap_.GetEntries(
reinterpret_cast<uint64_t>(start_address) + base::GetPageSize(),
2 * base::GetPageSize(), &entries));
ASSERT_EQ(entries.size(), 2u);
ASSERT_EQ(
PFN_FROM_TYPE_AND_OFFSET(entries[0].swap_type, entries[0].swap_offset),
1234u);
ASSERT_EQ(entries[0].pte_soft_dirty, false);
ASSERT_EQ(entries[0].page_present, false);
ASSERT_EQ(entries[0].page_swapped, true);
ASSERT_EQ(entries[0].page_exclusively_mapped, false);
ASSERT_EQ(entries[0].page_file_or_shared_anon, false);
ASSERT_EQ(
PFN_FROM_TYPE_AND_OFFSET(entries[1].swap_type, entries[1].swap_offset),
5551212u);
ASSERT_EQ(entries[1].pte_soft_dirty, true);
ASSERT_EQ(entries[1].page_present, false);
ASSERT_EQ(entries[1].page_swapped, false);
ASSERT_EQ(entries[1].page_exclusively_mapped, false);
ASSERT_EQ(entries[1].page_file_or_shared_anon, true);
}
TEST_F(PagemapTest, VectorResizedWhenIncorrectlySized) {
// This test validates that passing in an incorrectly sized vector is handled
// automatically.
void* start_address;
void* end_address;
CreateStorageForPages(4, &start_address, &end_address);
uint64_t page_map_entries[] = {PFN(8675309) | SOFT_DIRTY | PRESENT,
PFN(1234) | SWAPPED,
PFN(5551212) | SOFT_DIRTY | FILE_PAGE,
PFN(0xF00) | PRESENT | EXCLUSIVELY_MAPPED};
PutEntries(start_address, page_map_entries, countof(page_map_entries));
// We will read the two middle pages.
std::vector<Pagemap::PagemapEntry> entries;
entries.resize(1); // This is too short, it will be automatically resized.
ASSERT_TRUE(pagemap_.GetEntries(
reinterpret_cast<uint64_t>(start_address) + base::GetPageSize(),
2 * base::GetPageSize(), &entries));
ASSERT_EQ(entries.size(), 2u);
ASSERT_EQ(
PFN_FROM_TYPE_AND_OFFSET(entries[0].swap_type, entries[0].swap_offset),
1234u);
ASSERT_EQ(entries[0].pte_soft_dirty, false);
ASSERT_EQ(entries[0].page_present, false);
ASSERT_EQ(entries[0].page_swapped, true);
ASSERT_EQ(entries[0].page_exclusively_mapped, false);
ASSERT_EQ(entries[0].page_file_or_shared_anon, false);
ASSERT_EQ(
PFN_FROM_TYPE_AND_OFFSET(entries[1].swap_type, entries[1].swap_offset),
5551212u);
ASSERT_EQ(entries[1].pte_soft_dirty, true);
ASSERT_EQ(entries[1].page_present, false);
ASSERT_EQ(entries[1].page_swapped, false);
ASSERT_EQ(entries[1].page_exclusively_mapped, false);
ASSERT_EQ(entries[1].page_file_or_shared_anon, true);
}
} // namespace memory
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment