diff --git a/imgdiag/Android.bp b/imgdiag/Android.bp index a0354ab6c5..573fea57bf 100644 --- a/imgdiag/Android.bp +++ b/imgdiag/Android.bp @@ -28,7 +28,10 @@ package { cc_defaults { name: "imgdiag-defaults", host_supported: true, - srcs: ["imgdiag.cc"], + srcs: [ + "imgdiag.cc", + "page_util.cc", + ], defaults: ["art_defaults"], // Note that this tool needs to be built for both 32-bit and 64-bit since it requires @@ -103,3 +106,47 @@ art_cc_test { }, }, } + +cc_defaults { + name: "pageinfo-defaults", + host_supported: true, + srcs: [ + "page_info.cc", + "page_util.cc", + ], + defaults: ["art_defaults"], + + compile_multilib: "both", + + shared_libs: [ + "libbase", + ], + static_libs: [ + "libprocinfo", + ], + header_libs: [ + "art_cmdlineparser_headers", + ], + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, + symlink_preferred_arch: true, +} + +art_cc_binary { + name: "pageinfo", + defaults: ["pageinfo-defaults"], + shared_libs: [ + "libart", + "libartbase", + ], + apex_available: [ + "com.android.art", + "com.android.art.debug", + ], +} diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc index 26f8a00c28..43355ad543 100644 --- a/imgdiag/imgdiag.cc +++ b/imgdiag/imgdiag.cc @@ -14,8 +14,12 @@ * limitations under the License. */ +#include +#include #include #include +#include +#include #include #include @@ -26,9 +30,7 @@ #include #include -#include #include "android-base/stringprintf.h" - #include "art_field-inl.h" #include "art_method-inl.h" #include "base/array_ref.h" @@ -36,6 +38,7 @@ #include "base/string_view_cpp20.h" #include "base/unix_file/fd_file.h" #include "class_linker.h" +#include "cmdline.h" #include "gc/heap.h" #include "gc/space/image_space.h" #include "image-inl.h" @@ -44,14 +47,9 @@ #include "oat.h" #include "oat_file.h" #include "oat_file_manager.h" -#include "scoped_thread_state_change-inl.h" - +#include "page_util.h" #include "procinfo/process_map.h" -#include "cmdline.h" - -#include -#include -#include +#include "scoped_thread_state_change-inl.h" namespace art { @@ -1294,14 +1292,14 @@ class ImgDiagDumper { uint64_t page_count = 0xC0FFEE; // TODO: virtual_page_idx needs to be from the same process - int dirtiness = (IsPageDirty(&image_pagemap_file_, // Image-diff-pid procmap - &zygote_pagemap_file_, // Zygote procmap - &kpageflags_file_, - &kpagecount_file_, + int dirtiness = (IsPageDirty(image_pagemap_file_, // Image-diff-pid procmap + zygote_pagemap_file_, // Zygote procmap + kpageflags_file_, + kpagecount_file_, virtual_page_idx, // compare same page in image virtual_page_idx, // and zygote - &page_count, - error_msg)); + /*out*/ page_count, + /*out*/ *error_msg)); if (dirtiness < 0) { return false; } else if (dirtiness > 0) { @@ -1545,126 +1543,39 @@ class ImgDiagDumper { return true; } - // Note: On failure, `*page_frame_number` shall be clobbered. - static bool GetPageFrameNumber(File* page_map_file, - size_t virtual_page_index, - /*out*/ uint64_t* page_frame_number, - /*out*/ std::string* error_msg) { - CHECK(page_frame_number != nullptr); - return GetPageFrameNumbers(page_map_file, - virtual_page_index, - ArrayRef(page_frame_number, 1u), - error_msg); - } - - // Note: On failure, `page_frame_numbers[.]` shall be clobbered. - static bool GetPageFrameNumbers(File* page_map_file, - size_t virtual_page_index, - /*out*/ ArrayRef page_frame_numbers, - /*out*/ std::string* error_msg) { - CHECK(page_map_file != nullptr); - CHECK_NE(page_frame_numbers.size(), 0u); - CHECK(page_frame_numbers.data() != nullptr); - CHECK(error_msg != nullptr); - - // Read 64-bit entries from /proc/$pid/pagemap to get the physical page frame numbers. - if (!page_map_file->PreadFully(page_frame_numbers.data(), - page_frame_numbers.size() * kPageMapEntrySize, - virtual_page_index * kPageMapEntrySize)) { - *error_msg = StringPrintf("Failed to read the virtual page index entries from %s, error: %s", - page_map_file->GetPath().c_str(), - strerror(errno)); - return false; - } - - // Extract page frame numbers from pagemap entries. - for (uint64_t& page_frame_number : page_frame_numbers) { - page_frame_number &= kPageFrameNumberMask; - } - - return true; - } - - // Note: On failure, `page_flags_or_counts[.]` shall be clobbered. - static bool GetPageFlagsOrCounts(File* kpage_file, - ArrayRef page_frame_numbers, - /*out*/ ArrayRef page_flags_or_counts, - /*out*/ std::string* error_msg) { - static_assert(kPageFlagsEntrySize == kPageCountEntrySize, "entry size check"); - CHECK_NE(page_frame_numbers.size(), 0u); - CHECK_EQ(page_flags_or_counts.size(), page_frame_numbers.size()); - CHECK(kpage_file != nullptr); - CHECK(page_frame_numbers.data() != nullptr); - CHECK(page_flags_or_counts.data() != nullptr); - CHECK(error_msg != nullptr); - - size_t size = page_frame_numbers.size(); - size_t i = 0; - while (i != size) { - size_t start = i; - ++i; - while (i != size && page_frame_numbers[i] - page_frame_numbers[start] == i - start) { - ++i; - } - // Read 64-bit entries from /proc/kpageflags or /proc/kpagecount. - if (!kpage_file->PreadFully(page_flags_or_counts.data() + start, - (i - start) * kPageMapEntrySize, - page_frame_numbers[start] * kPageFlagsEntrySize)) { - *error_msg = StringPrintf("Failed to read the page flags or counts from %s, error: %s", - kpage_file->GetPath().c_str(), - strerror(errno)); - return false; - } - } - - return true; - } - - static int IsPageDirty(File* page_map_file, - File* clean_pagemap_file, - File* kpageflags_file, - File* kpagecount_file, + static int IsPageDirty(File& page_map_file, + File& clean_pagemap_file, + File& kpageflags_file, + File& kpagecount_file, size_t virtual_page_idx, size_t clean_virtual_page_idx, // Out parameters: - uint64_t* page_count, std::string* error_msg) { - CHECK(page_map_file != nullptr); - CHECK(clean_pagemap_file != nullptr); - CHECK_NE(page_map_file, clean_pagemap_file); - CHECK(kpageflags_file != nullptr); - CHECK(kpagecount_file != nullptr); - CHECK(page_count != nullptr); - CHECK(error_msg != nullptr); + uint64_t& page_count, + std::string& error_msg) { + CHECK_NE(page_map_file.GetPath(), clean_pagemap_file.GetPath()); // Constants are from https://www.kernel.org/doc/Documentation/vm/pagemap.txt uint64_t page_frame_number = 0; - if (!GetPageFrameNumber(page_map_file, virtual_page_idx, &page_frame_number, error_msg)) { + if (!GetPageFrameNumber(page_map_file, virtual_page_idx, page_frame_number, error_msg)) { return -1; } uint64_t page_frame_number_clean = 0; - if (!GetPageFrameNumber(clean_pagemap_file, clean_virtual_page_idx, &page_frame_number_clean, - error_msg)) { + if (!GetPageFrameNumber( + clean_pagemap_file, clean_virtual_page_idx, page_frame_number_clean, error_msg)) { return -1; } // Read 64-bit entry from /proc/kpageflags to get the dirty bit for a page uint64_t kpage_flags_entry = 0; - if (!kpageflags_file->PreadFully(&kpage_flags_entry, - kPageFlagsEntrySize, - page_frame_number * kPageFlagsEntrySize)) { - *error_msg = StringPrintf("Failed to read the page flags from %s", - kpageflags_file->GetPath().c_str()); + if (!GetPageFlagsOrCount( + kpageflags_file, page_frame_number, /*out*/ kpage_flags_entry, error_msg)) { return -1; } // Read 64-bit entyry from /proc/kpagecount to get mapping counts for a page - if (!kpagecount_file->PreadFully(page_count /*out*/, - kPageCountEntrySize, - page_frame_number * kPageCountEntrySize)) { - *error_msg = StringPrintf("Failed to read the page count from %s", - kpagecount_file->GetPath().c_str()); + if (!GetPageFlagsOrCount(kpagecount_file, page_frame_number, /*out*/ page_count, error_msg)) { return -1; } @@ -1673,21 +1584,6 @@ class ImgDiagDumper { // The page frame must be memory mapped CHECK_NE(kpage_flags_entry & kPageFlagsMmapMask, 0u); - // Page is dirty, i.e. has diverged from file, if the 4th bit is set to 1 - bool flags_dirty = (kpage_flags_entry & kPageFlagsDirtyMask) != 0; - - // page_frame_number_clean must come from the *same* process - // but a *different* mmap than page_frame_number - if (flags_dirty) { - // FIXME: This check sometimes fails and the reason is not understood. b/123852774 - if (page_frame_number != page_frame_number_clean) { - LOG(ERROR) << "Check failed: page_frame_number != page_frame_number_clean " - << "(page_frame_number=" << page_frame_number - << ", page_frame_number_clean=" << page_frame_number_clean << ")" - << " count: " << *page_count << " flags: 0x" << std::hex << kpage_flags_entry; - } - } - return (page_frame_number != page_frame_number_clean) ? 1 : 0; } @@ -1714,17 +1610,6 @@ class ImgDiagDumper { return BaseName(std::string(image_location)); } - static constexpr size_t kPageMapEntrySize = sizeof(uint64_t); - // bits 0-54 [in /proc/$pid/pagemap] - static constexpr uint64_t kPageFrameNumberMask = (1ULL << 55) - 1; - - static constexpr size_t kPageFlagsEntrySize = sizeof(uint64_t); - static constexpr size_t kPageCountEntrySize = sizeof(uint64_t); - static constexpr uint64_t kPageFlagsDirtyMask = (1ULL << 4); // in /proc/kpageflags - static constexpr uint64_t kPageFlagsNoPageMask = (1ULL << 20); // in /proc/kpageflags - static constexpr uint64_t kPageFlagsMmapMask = (1ULL << 11); // in /proc/kpageflags - - std::ostream* os_; pid_t image_diff_pid_; // Dump image diff against boot.art if pid is non-negative pid_t zygote_diff_pid_; // Dump image diff against zygote boot.art if pid is non-negative diff --git a/imgdiag/page_info.cc b/imgdiag/page_info.cc new file mode 100644 index 0000000000..8f4be090d5 --- /dev/null +++ b/imgdiag/page_info.cc @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "android-base/parseint.h" +#include "android-base/stringprintf.h" +#include "base/os.h" +#include "base/unix_file/fd_file.h" +#include "cmdline.h" +#include "page_util.h" +#include "procinfo/process_map.h" +#include "scoped_thread_state_change-inl.h" + +namespace art { + +using android::base::StringPrintf; + +namespace { + +struct ProcFiles { + // A File for reading /proc//mem. + File mem; + // A File for reading /proc//pagemap. + File pagemap; + // A File for reading /proc/kpageflags. + File kpageflags; + // A File for reading /proc/kpagecount. + File kpagecount; +}; + +bool OpenFile(const char* file_name, /*out*/ File& file, /*out*/ std::string& error_msg) { + std::unique_ptr file_ptr = std::unique_ptr{OS::OpenFileForReading(file_name)}; + if (file_ptr == nullptr) { + error_msg = StringPrintf("Failed to open file: %s", file_name); + return false; + } + file = std::move(*file_ptr); + return true; +} + +bool OpenProcFiles(pid_t pid, /*out*/ ProcFiles& files, /*out*/ std::string& error_msg) { + if (!OpenFile("/proc/kpageflags", files.kpageflags, error_msg)) { + return false; + } + if (!OpenFile("/proc/kpagecount", files.kpagecount, error_msg)) { + return false; + } + std::string mem_file_name = + StringPrintf("/proc/%ld/mem", static_cast(pid)); // NOLINT [runtime/int] + if (!OpenFile(mem_file_name.c_str(), files.mem, error_msg)) { + return false; + } + std::string pagemap_file_name = + StringPrintf("/proc/%ld/pagemap", static_cast(pid)); // NOLINT [runtime/int] + if (!OpenFile(pagemap_file_name.c_str(), files.pagemap, error_msg)) { + return false; + } + return true; +} + +void DumpPageInfo(uint64_t virtual_page_index, ProcFiles& proc_files, std::ostream& os) { + const uint64_t virtual_page_addr = virtual_page_index * kPageSize; + os << "Virtual page index: " << virtual_page_index << "\n"; + os << "Virtual page addr: " << virtual_page_addr << "\n"; + + std::string error_msg; + uint64_t page_frame_number = -1; + if (!GetPageFrameNumber( + proc_files.pagemap, virtual_page_index, /*out*/ page_frame_number, /*out*/ error_msg)) { + os << "Failed to get page frame number: " << error_msg << "\n"; + return; + } + os << "Page frame number: " << page_frame_number << "\n"; + + uint64_t page_count = -1; + if (!GetPageFlagsOrCount(proc_files.kpagecount, + page_frame_number, + /*out*/ page_count, + /*out*/ error_msg)) { + os << "Failed to get page count: " << error_msg << "\n"; + return; + } + os << "kpagecount: " << page_count << "\n"; + + uint64_t page_flags = 0; + if (!GetPageFlagsOrCount(proc_files.kpageflags, + page_frame_number, + /*out*/ page_flags, + /*out*/ error_msg)) { + os << "Failed to get page flags: " << error_msg << "\n"; + return; + } + os << "kpageflags: " << page_flags << "\n"; + + if (page_count != 0) { + std::vector page_contents(kPageSize); + if (!proc_files.mem.PreadFully(page_contents.data(), page_contents.size(), virtual_page_addr)) { + os << "Failed to read page contents\n"; + return; + } + os << "Zero bytes: " << std::count(std::begin(page_contents), std::end(page_contents), 0) + << "\n"; + } +} + +struct MapPageCounts { + // Present pages count. + uint64_t pages = 0; + // Non-present pages count. + uint64_t non_present_pages = 0; + // Private (kpagecount == 1) zero page count. + uint64_t private_zero_pages = 0; + // Shared (kpagecount > 1) zero page count. + uint64_t shared_zero_pages = 0; + // Physical frame numbers of zero pages. + std::unordered_set zero_page_pfns; + + // Memory map name. + std::string name; + // Memory map start address. + uint64_t start = 0; + // Memory map end address. + uint64_t end = 0; +}; + +bool GetMapPageCounts(ProcFiles& proc_files, + const android::procinfo::MapInfo& map_info, + MapPageCounts& map_page_counts, + std::string& error_msg) { + map_page_counts.name = map_info.name; + map_page_counts.start = map_info.start; + map_page_counts.end = map_info.end; + std::vector page_contents(kPageSize); + for (uint64_t begin = map_info.start; begin < map_info.end; begin += kPageSize) { + const size_t virtual_page_index = begin / kPageSize; + uint64_t page_frame_number = -1; + if (!GetPageFrameNumber(proc_files.pagemap, virtual_page_index, page_frame_number, error_msg)) { + return false; + } + uint64_t page_count = -1; + if (!GetPageFlagsOrCounts(proc_files.kpagecount, + ArrayRef(&page_frame_number, 1), + /*out*/ ArrayRef(&page_count, 1), + /*out*/ error_msg)) { + return false; + } + + const auto is_zero_page = [](const std::vector& page) { + const auto non_zero_it = + std::find_if(std::begin(page), std::end(page), [](uint8_t b) { return b != 0; }); + return non_zero_it == std::end(page); + }; + + if (page_count == 0) { + map_page_counts.non_present_pages += 1; + continue; + } + + // Handle present page. + if (!proc_files.mem.PreadFully(page_contents.data(), page_contents.size(), begin)) { + error_msg = StringPrintf( + "Failed to read present page %" PRIx64 " for mapping %s\n", begin, map_info.name.c_str()); + return false; + } + const bool is_zero = is_zero_page(page_contents); + const bool is_private = (page_count == 1); + map_page_counts.pages += 1; + if (is_zero) { + map_page_counts.zero_page_pfns.insert(page_frame_number); + if (is_private) { + map_page_counts.private_zero_pages += 1; + } else { + map_page_counts.shared_zero_pages += 1; + } + } + } + return true; +} + +void CountZeroPages(pid_t pid, ProcFiles& proc_files, std::ostream& os) { + std::vector proc_maps; + if (!android::procinfo::ReadProcessMaps(pid, &proc_maps)) { + os << "Could not read process maps for " << pid; + return; + } + + MapPageCounts total; + std::vector stats; + for (const android::procinfo::MapInfo& map_info : proc_maps) { + MapPageCounts map_page_counts; + std::string error_msg; + if (!GetMapPageCounts(proc_files, map_info, map_page_counts, error_msg)) { + os << "Error getting map page counts for: " << map_info.name << "\n" << error_msg << "\n\n"; + continue; + } + total.pages += map_page_counts.pages; + total.private_zero_pages += map_page_counts.private_zero_pages; + total.shared_zero_pages += map_page_counts.shared_zero_pages; + total.non_present_pages += map_page_counts.non_present_pages; + total.zero_page_pfns.insert(std::begin(map_page_counts.zero_page_pfns), + std::end(map_page_counts.zero_page_pfns)); + stats.push_back(std::move(map_page_counts)); + } + + // Sort by different page counts, descending. + const auto sort_by_private_zero_pages = [](const auto& stats1, const auto& stats2) { + return stats1.private_zero_pages > stats2.private_zero_pages; + }; + const auto sort_by_shared_zero_pages = [](const auto& stats1, const auto& stats2) { + return stats1.shared_zero_pages > stats2.shared_zero_pages; + }; + const auto sort_by_unique_zero_pages = [](const auto& stats1, const auto& stats2) { + return stats1.zero_page_pfns.size() > stats2.zero_page_pfns.size(); + }; + + // Print up to `max_lines` entries. + const auto print_stats = [&stats, &os](size_t max_lines) { + for (const MapPageCounts& map_page_counts : stats) { + if (max_lines == 0) { + return; + } + // Skip entries with no present pages. + if (map_page_counts.pages == 0) { + continue; + } + max_lines -= 1; + os << StringPrintf("%" PRIx64 "-%" PRIx64 " %s: pages=%" PRIu64 + ", private_zero_pages=%" PRIu64 ", shared_zero_pages=%" PRIu64 + ", unique_zero_pages=%" PRIu64 ", non_present_pages=%" PRIu64 "\n", + map_page_counts.start, + map_page_counts.end, + map_page_counts.name.c_str(), + map_page_counts.pages, + map_page_counts.private_zero_pages, + map_page_counts.shared_zero_pages, + uint64_t{map_page_counts.zero_page_pfns.size()}, + map_page_counts.non_present_pages); + } + }; + + os << StringPrintf("total_pages=%" PRIu64 ", total_private_zero_pages=%" PRIu64 + ", total_shared_zero_pages=%" PRIu64 ", total_unique_zero_pages=%" PRIu64 + ", total_non_present_pages=%" PRIu64 "\n", + total.pages, + total.private_zero_pages, + total.shared_zero_pages, + uint64_t{total.zero_page_pfns.size()}, + total.non_present_pages); + os << "\n\n"; + + const size_t top_lines = std::min(size_t{20}, stats.size()); + std::partial_sort( + std::begin(stats), std::begin(stats) + top_lines, std::end(stats), sort_by_unique_zero_pages); + os << "Top " << top_lines << " maps by unique zero pages (unique PFN count)\n"; + print_stats(top_lines); + os << "\n\n"; + + std::partial_sort(std::begin(stats), + std::begin(stats) + top_lines, + std::end(stats), + sort_by_private_zero_pages); + os << "Top " << top_lines << " maps by private zero pages (kpagecount == 1)\n"; + print_stats(top_lines); + os << "\n\n"; + + std::partial_sort( + std::begin(stats), std::begin(stats) + top_lines, std::end(stats), sort_by_shared_zero_pages); + os << "Top " << top_lines << " maps by shared zero pages (kpagecount > 1)\n"; + print_stats(top_lines); + os << "\n\n"; + + std::sort(std::begin(stats), std::end(stats), sort_by_unique_zero_pages); + os << "All maps by unique zero pages (unique PFN count)\n"; + print_stats(stats.size()); + os << "\n\n"; +} + +} // namespace + +int PageInfo(std::ostream& os, + pid_t pid, + bool count_zero_pages, + std::optional virtual_page_index) { + ProcFiles proc_files; + std::string error_msg; + if (!OpenProcFiles(pid, proc_files, error_msg)) { + os << error_msg; + return EXIT_FAILURE; + } + if (virtual_page_index != std::nullopt) { + DumpPageInfo(virtual_page_index.value(), proc_files, os); + } + if (count_zero_pages) { + CountZeroPages(pid, proc_files, os); + } + return EXIT_SUCCESS; +} + +struct PageInfoArgs : public CmdlineArgs { + protected: + using Base = CmdlineArgs; + + ParseStatus ParseCustom(const char* raw_option, + size_t raw_option_length, + std::string* error_msg) override { + DCHECK_EQ(strlen(raw_option), raw_option_length); + { + ParseStatus base_parse = Base::ParseCustom(raw_option, raw_option_length, error_msg); + if (base_parse != kParseUnknownArgument) { + return base_parse; + } + } + + std::string_view option(raw_option, raw_option_length); + if (StartsWith(option, "--pid=")) { + // static_assert(std::is_signed_t + const char* value = raw_option + strlen("--pid="); + if (!android::base::ParseInt(value, &pid_)) { + *error_msg = "Failed to parse pid"; + return kParseError; + } + } else if (option == "--count-zero-pages") { + count_zero_pages_ = true; + } else if (StartsWith(option, "--dump-page-info=")) { + const char* value = raw_option + strlen("--dump-page-info="); + virtual_page_index_ = 0; + if (!android::base::ParseUint(value, &virtual_page_index_.value())) { + *error_msg = "Failed to parse virtual page index"; + return kParseError; + } + } else { + return kParseUnknownArgument; + } + + return kParseOk; + } + + ParseStatus ParseChecks(std::string* error_msg) override { + // Perform the parent checks. + ParseStatus parent_checks = Base::ParseChecks(error_msg); + if (parent_checks != kParseOk) { + return parent_checks; + } + if (pid_ == -1) { + *error_msg = "Missing --pid="; + return kParseError; + } + + // Perform our own checks. + if (kill(pid_, /*sig*/ 0) != 0) { // No signal is sent, perform error-checking only. + // Check if the pid exists before proceeding. + if (errno == ESRCH) { + *error_msg = "Process specified does not exist, pid: " + std::to_string(pid_); + } else { + *error_msg = StringPrintf("Failed to check process status: %s", strerror(errno)); + } + return kParseError; + } + return kParseOk; + } + + std::string GetUsage() const override { + std::string usage; + + usage += + "Usage: pageinfo [options] ...\n" + " Example: pageinfo --pid=$(pidof system_server) --count-zero-pages\n" + " Example: adb shell pageinfo --pid=$(pid system_server) --dump-page-info=0x70000000\n" + "\n"; + + usage += Base::GetUsage(); + + usage += + " --pid=: PID of the process to analyze.\n" + " --count-zero-pages: output zero filled page stats for memory mappings of " + " process.\n" + " --dump-page-info=: output PFN, kpagecount and kpageflags of a " + "virtual page in process memory space.\n"; + + return usage; + } + + public: + pid_t pid_ = -1; + bool count_zero_pages_ = false; + std::optional virtual_page_index_; +}; + +struct PageInfoMain : public CmdlineMain { + bool ExecuteWithoutRuntime() override { + CHECK(args_ != nullptr); + CHECK(args_->os_ != nullptr); + + return PageInfo( + *args_->os_, args_->pid_, args_->count_zero_pages_, args_->virtual_page_index_) == + EXIT_SUCCESS; + } + + bool NeedsRuntime() override { return false; } +}; + +} // namespace art + +int main(int argc, char** argv) { + art::PageInfoMain main; + return main.Main(argc, argv); +} diff --git a/imgdiag/page_util.cc b/imgdiag/page_util.cc new file mode 100644 index 0000000000..0b765f48c0 --- /dev/null +++ b/imgdiag/page_util.cc @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "page_util.h" + +#include "android-base/stringprintf.h" + +namespace art { + +using android::base::StringPrintf; + +bool GetPageFlagsOrCount(art::File& kpage_file, + uint64_t page_frame_number, + /*out*/ uint64_t& page_flags_or_count, + /*out*/ std::string& error_msg) { + return GetPageFlagsOrCounts(kpage_file, + ArrayRef(&page_frame_number, 1u), + ArrayRef(&page_flags_or_count, 1u), + error_msg); +} + +bool GetPageFlagsOrCounts(File& kpage_file, + ArrayRef page_frame_numbers, + /*out*/ ArrayRef page_flags_or_counts, + /*out*/ std::string& error_msg) { + static_assert(kPageFlagsEntrySize == kPageCountEntrySize, "entry size check"); + CHECK_NE(page_frame_numbers.size(), 0u); + CHECK_EQ(page_flags_or_counts.size(), page_frame_numbers.size()); + CHECK(page_frame_numbers.data() != nullptr); + CHECK(page_flags_or_counts.data() != nullptr); + + size_t size = page_frame_numbers.size(); + size_t i = 0; + while (i != size) { + size_t start = i; + ++i; + while (i != size && page_frame_numbers[i] - page_frame_numbers[start] == i - start) { + ++i; + } + // Read 64-bit entries from /proc/kpageflags or /proc/kpagecount. + if (!kpage_file.PreadFully(page_flags_or_counts.data() + start, + (i - start) * kPageMapEntrySize, + page_frame_numbers[start] * kPageFlagsEntrySize)) { + error_msg = StringPrintf("Failed to read the page flags or counts from %s, error: %s", + kpage_file.GetPath().c_str(), + strerror(errno)); + return false; + } + } + + return true; +} + +bool GetPageFrameNumber(File& page_map_file, + size_t virtual_page_index, + /*out*/ uint64_t& page_frame_number, + /*out*/ std::string& error_msg) { + return GetPageFrameNumbers( + page_map_file, virtual_page_index, ArrayRef(&page_frame_number, 1u), error_msg); +} + +bool GetPageFrameNumbers(File& page_map_file, + size_t virtual_page_index, + /*out*/ ArrayRef page_frame_numbers, + /*out*/ std::string& error_msg) { + CHECK_NE(page_frame_numbers.size(), 0u); + CHECK(page_frame_numbers.data() != nullptr); + + // Read 64-bit entries from /proc/$pid/pagemap to get the physical page frame numbers. + if (!page_map_file.PreadFully(page_frame_numbers.data(), + page_frame_numbers.size() * kPageMapEntrySize, + virtual_page_index * kPageMapEntrySize)) { + error_msg = StringPrintf("Failed to read virtual page index entries from %s, error: %s", + page_map_file.GetPath().c_str(), + strerror(errno)); + return false; + } + + // Extract page frame numbers from pagemap entries. + for (uint64_t& page_frame_number : page_frame_numbers) { + page_frame_number &= kPageFrameNumberMask; + } + + return true; +} + +} // namespace art diff --git a/imgdiag/page_util.h b/imgdiag/page_util.h new file mode 100644 index 0000000000..dc3ba25581 --- /dev/null +++ b/imgdiag/page_util.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_IMGDIAG_PAGE_UTIL_H_ +#define ART_IMGDIAG_PAGE_UTIL_H_ + +#include + +#include "base/array_ref.h" +#include "base/os.h" +#include "base/unix_file/fd_file.h" + +namespace art { +static constexpr size_t kPageMapEntrySize = sizeof(uint64_t); +// bits 0-54 [in /proc/$pid/pagemap] +static constexpr uint64_t kPageFrameNumberMask = (1ULL << 55) - 1; + +static constexpr size_t kPageFlagsEntrySize = sizeof(uint64_t); +static constexpr size_t kPageCountEntrySize = sizeof(uint64_t); +static constexpr uint64_t kPageFlagsDirtyMask = (1ULL << 4); // in /proc/kpageflags +static constexpr uint64_t kPageFlagsNoPageMask = (1ULL << 20); // in /proc/kpageflags +static constexpr uint64_t kPageFlagsMmapMask = (1ULL << 11); // in /proc/kpageflags + +// Note: On failure, `page_flags_or_counts[.]` shall be clobbered. +bool GetPageFlagsOrCount(art::File& kpage_file, + uint64_t page_frame_number, + /*out*/ uint64_t& page_flags_or_count, + /*out*/ std::string& error_msg); + +bool GetPageFlagsOrCounts(art::File& kpage_file, + ArrayRef page_frame_numbers, + /*out*/ ArrayRef page_flags_or_counts, + /*out*/ std::string& error_msg); + +// Note: On failure, `*page_frame_number` shall be clobbered. +bool GetPageFrameNumber(art::File& page_map_file, + size_t virtual_page_index, + /*out*/ uint64_t& page_frame_number, + /*out*/ std::string& error_msg); + +bool GetPageFrameNumbers(art::File& page_map_file, + size_t virtual_page_index, + /*out*/ ArrayRef page_frame_numbers, + /*out*/ std::string& error_msg); + +} // namespace art + +#endif // ART_IMGDIAG_PAGE_UTIL_H_