| // 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/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 |