diff options
author | 2025-03-04 16:56:31 +0000 | |
---|---|---|
committer | 2025-03-10 07:50:14 -0700 | |
commit | 1e7f64367f8e396391d52fe037e662c8b08c80cd (patch) | |
tree | e1310d5174e186b08523791f3e783375a1aef779 | |
parent | e8b13bcd702c20176dd8acfab6f130f0d9eec736 (diff) |
Add find_unshared_pages tool
The tool goes through all memory maps of two processes and finds pages
that have equal contents but are not shared.
Test: find_unshared_pages --pid1=$(pidof system_server) --pid2=$(pidof com.android.camera2)
Change-Id: I1c5bbb18c9854c13f6112a87dbea2d806421d2bb
-rw-r--r-- | build/apex/Android.bp | 6 | ||||
-rw-r--r-- | imgdiag/Android.bp | 24 | ||||
-rw-r--r-- | imgdiag/find_unshared_pages.cc | 282 | ||||
-rw-r--r-- | imgdiag/page_info.cc | 41 | ||||
-rw-r--r-- | imgdiag/page_util.cc | 30 | ||||
-rw-r--r-- | imgdiag/page_util.h | 13 |
6 files changed, 348 insertions, 48 deletions
diff --git a/build/apex/Android.bp b/build/apex/Android.bp index d2fd8f9e1e..f91b7863ee 100644 --- a/build/apex/Android.bp +++ b/build/apex/Android.bp @@ -220,7 +220,11 @@ apex_test { native_shared_libs: ["libart"], multilib: { both: { - binaries: ["imgdiag"], + binaries: [ + "imgdiag", + "pageinfo", + "find_unshared_pages", + ], }, first: { binaries: ["odrefresh"], diff --git a/imgdiag/Android.bp b/imgdiag/Android.bp index cb4efa8c49..c20d30b9ac 100644 --- a/imgdiag/Android.bp +++ b/imgdiag/Android.bp @@ -110,10 +110,9 @@ art_cc_test { } cc_defaults { - name: "pageinfo-defaults", + name: "page_util-defaults", host_supported: true, srcs: [ - "page_info.cc", "page_util.cc", ], defaults: ["art_defaults"], @@ -122,6 +121,8 @@ cc_defaults { shared_libs: [ "libbase", + "libartbase", + "libart", ], static_libs: [ "libprocinfo", @@ -142,10 +143,21 @@ cc_defaults { art_cc_binary { name: "pageinfo", - defaults: ["pageinfo-defaults"], - shared_libs: [ - "libart", - "libartbase", + defaults: ["page_util-defaults"], + srcs: [ + "page_info.cc", + ], + apex_available: [ + "com.android.art", + "com.android.art.debug", + ], +} + +art_cc_binary { + name: "find_unshared_pages", + defaults: ["page_util-defaults"], + srcs: [ + "find_unshared_pages.cc", ], apex_available: [ "com.android.art", diff --git a/imgdiag/find_unshared_pages.cc b/imgdiag/find_unshared_pages.cc new file mode 100644 index 0000000000..993a8d3f81 --- /dev/null +++ b/imgdiag/find_unshared_pages.cc @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2025 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 <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <ostream> +#include <string> +#include <vector> + +#include "android-base/parseint.h" +#include "android-base/stringprintf.h" +#include "base/os.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; + +struct PageInfo { + // Page start address. + uint64_t start = -1; + // Page end address. + uint64_t end = -1; + // Number of times the physical page is mapped. + uint64_t page_count = -1; + // Physical frame number of the page. + uint64_t pfn = -1; + // Number of zero bytes in the page. + uint64_t zero_bytes_count = -1; + // Memory region of the page. + const android::procinfo::MapInfo* mem_map = nullptr; +}; + +struct ProcPages { + // Memory maps of the process. + std::vector<android::procinfo::MapInfo> maps; + // Page contents hash -> PageInfo + std::unordered_map<size_t, std::vector<PageInfo>> pages; +}; + +bool ReadProcessPages( + std::ostream& os, pid_t pid, ProcPages& proc_pages, ProcFiles& proc_files, uint64_t page_size) { + if (!android::procinfo::ReadProcessMaps(pid, &proc_pages.maps)) { + os << "Could not read process maps for " << pid; + return false; + } + + std::string error_msg; + std::vector<uint8_t> page_contents(page_size); + for (const android::procinfo::MapInfo& map_info : proc_pages.maps) { + for (uint64_t start = map_info.start; start < map_info.end; start += page_size) { + PageInfo page_info; + page_info.start = start; + page_info.end = start + page_size; + page_info.mem_map = &map_info; + const size_t virtual_page_index = start / page_size; + if (!GetPageFrameNumber(proc_files.pagemap, virtual_page_index, page_info.pfn, error_msg)) { + os << error_msg; + return false; + } + if (!GetPageFlagsOrCounts(proc_files.kpagecount, + ArrayRef<const uint64_t>(&page_info.pfn, 1), + /*out*/ ArrayRef<uint64_t>(&page_info.page_count, 1), + /*out*/ error_msg)) { + os << error_msg << std::endl; + os << "mem_map name: " << map_info.name << std::endl; + os << "pfn: " << page_info.pfn << std::endl; + os << "page_start: " << page_info.start << std::endl; + os << "mem_map start: " << map_info.start << std::endl; + // start = map_info.end; + continue; + } + + if (page_info.page_count == 0) { + // Page is not present in memory. + continue; + } + + // Handle present page. + if (!proc_files.mem.PreadFully(page_contents.data(), page_contents.size(), start)) { + os << StringPrintf("Failed to read present page %" PRIx64 " for mem_map %s\n", + start, + map_info.name.c_str()); + return false; + } + + page_info.zero_bytes_count = + static_cast<uint64_t>(std::count(page_contents.begin(), page_contents.end(), 0)); + + const size_t content_hash = DataHash::HashBytes(page_contents.data(), page_contents.size()); + proc_pages.pages[content_hash].push_back(page_info); + } + } + + return true; +} + +int FindUnsharedPages(std::ostream& os, pid_t pid1, pid_t pid2, size_t page_size) { + ProcFiles proc_files1; + ProcFiles proc_files2; + std::string error_msg; + if (!OpenProcFiles(pid1, proc_files1, error_msg)) { + os << error_msg; + return EXIT_FAILURE; + } + if (!OpenProcFiles(pid2, proc_files2, error_msg)) { + os << error_msg; + return EXIT_FAILURE; + } + + ProcPages proc_pages1; + if (!ReadProcessPages(os, pid1, proc_pages1, proc_files1, page_size)) { + return EXIT_FAILURE; + } + + ProcPages proc_pages2; + if (!ReadProcessPages(os, pid2, proc_pages2, proc_files2, page_size)) { + return EXIT_FAILURE; + } + + for (const auto& [hash, pages1] : proc_pages1.pages) { + // Skip zero pages. + if (pages1.front().zero_bytes_count == page_size) { + continue; + } + + // Find pages with the same content in the second process. + if (proc_pages2.pages.find(hash) != proc_pages2.pages.end()) { + const auto& pages2 = proc_pages2.pages[hash]; + + std::unordered_set<uint64_t> pfns1, pfns2; + for (const auto& p : pages1) { + pfns1.insert(p.pfn); + } + for (const auto& p : pages2) { + pfns2.insert(p.pfn); + } + + const bool is_different = (pfns1 != pfns2); + if (is_different) { + os << "\nDuplicate pages (pfn, start_addr, mem_map, zero_bytes_count)\nPID1:\n"; + for (const auto& page1 : pages1) { + os << ART_FORMAT("\t{} {} {} {}\n", + page1.pfn, + page1.start, + page1.mem_map->name, + page1.zero_bytes_count); + } + os << "PID2:\n"; + for (const auto& page2 : pages2) { + os << ART_FORMAT("\t{} {} {} {}\n", + page2.pfn, + page2.start, + page2.mem_map->name, + page2.zero_bytes_count); + } + } + } + } + + return EXIT_SUCCESS; +} + +struct FindUnsharedPagesArgs : 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 (option.starts_with("--pid1=")) { + const char* value = raw_option + strlen("--pid1="); + if (!android::base::ParseInt(value, &pid1_)) { + *error_msg = "Failed to parse pid1"; + return kParseError; + } + } else if (option.starts_with("--pid2=")) { + const char* value = raw_option + strlen("--pid2="); + if (!android::base::ParseInt(value, &pid2_)) { + *error_msg = "Failed to parse pid2"; + return kParseError; + } + } else { + return kParseUnknownArgument; + } + + return kParseOk; + } + + ParseStatus ParseChecks(std::string* error_msg) override { + ParseStatus parent_checks = Base::ParseChecks(error_msg); + if (parent_checks != kParseOk) { + return parent_checks; + } + + if (pid1_ == -1 || pid2_ == -1) { + *error_msg = "Missing --pid="; + return kParseError; + } + + for (pid_t pid : {pid1_, pid2_}) { + 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: find_unshared_pages [options] ...\n" + " Example: find_unshared_pages --pid1=$(pidof system_server) --pid2=$(pidof " + "com.android.camera2)\n" + "\n"; + + usage += Base::GetUsage(); + + usage += " --pid1=<pid> --pid2=<pid>: PIDs of the processes to analyze.\n"; + + return usage; + } + + public: + pid_t pid1_ = -1; + pid_t pid2_ = -1; +}; + +struct FindUnsharedPagesMain : public CmdlineMain<FindUnsharedPagesArgs> { + bool ExecuteWithoutRuntime() override { + CHECK(args_ != nullptr); + CHECK(args_->os_ != nullptr); + + return FindUnsharedPages(*args_->os_, args_->pid1_, args_->pid2_, MemMap::GetPageSize()) == + EXIT_SUCCESS; + } + + bool NeedsRuntime() override { return false; } +}; + +} // namespace art + +int main(int argc, char** argv) { + art::FindUnsharedPagesMain main; + return main.Main(argc, argv); +} diff --git a/imgdiag/page_info.cc b/imgdiag/page_info.cc index 025580a849..cdd1399d1a 100644 --- a/imgdiag/page_info.cc +++ b/imgdiag/page_info.cc @@ -41,47 +41,6 @@ using android::base::StringPrintf; namespace { -struct ProcFiles { - // A File for reading /proc/<pid>/mem. - File mem; - // A File for reading /proc/<pid>/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> file_ptr = std::unique_ptr<File>{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<long>(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<long>(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, size_t page_size) { const uint64_t virtual_page_addr = virtual_page_index * page_size; diff --git a/imgdiag/page_util.cc b/imgdiag/page_util.cc index 0b765f48c0..66ea978c32 100644 --- a/imgdiag/page_util.cc +++ b/imgdiag/page_util.cc @@ -97,4 +97,34 @@ bool GetPageFrameNumbers(File& page_map_file, return true; } +bool OpenFile(const char* file_name, /*out*/ File& file, /*out*/ std::string& error_msg) { + std::unique_ptr<File> file_ptr = std::unique_ptr<File>{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<long>(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<long>(pid)); // NOLINT [runtime/int] + if (!OpenFile(pagemap_file_name.c_str(), files.pagemap, error_msg)) { + return false; + } + return true; +} + } // namespace art diff --git a/imgdiag/page_util.h b/imgdiag/page_util.h index dc3ba25581..1553868ff1 100644 --- a/imgdiag/page_util.h +++ b/imgdiag/page_util.h @@ -56,6 +56,19 @@ bool GetPageFrameNumbers(art::File& page_map_file, /*out*/ ArrayRef<uint64_t> page_frame_numbers, /*out*/ std::string& error_msg); +struct ProcFiles { + // A File for reading /proc/<pid>/mem. + File mem; + // A File for reading /proc/<pid>/pagemap. + File pagemap; + // A File for reading /proc/kpageflags. + File kpageflags; + // A File for reading /proc/kpagecount. + File kpagecount; +}; + +bool OpenProcFiles(pid_t pid, /*out*/ ProcFiles& files, /*out*/ std::string& error_msg); + } // namespace art #endif // ART_IMGDIAG_PAGE_UTIL_H_ |