|  | // Copyright 2020 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chromeos/ash/components/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/compiler_specific.h" | 
|  | #include "base/files/scoped_file.h" | 
|  | #include "base/memory/page_size.h" | 
|  | #include "base/posix/eintr_wrapper.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace ash { | 
|  | 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 = UNSAFE_TODO(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 ash |