• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <signal.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 
23 #include <ostream>
24 #include <string>
25 #include <vector>
26 
27 #include "android-base/parseint.h"
28 #include "android-base/stringprintf.h"
29 #include "base/os.h"
30 #include "cmdline.h"
31 #include "page_util.h"
32 #include "procinfo/process_map.h"
33 #include "scoped_thread_state_change-inl.h"
34 
35 namespace art {
36 
37 using android::base::StringPrintf;
38 
39 struct PageInfo {
40   // Page start address.
41   uint64_t start = -1;
42   // Page end address.
43   uint64_t end = -1;
44   // Number of times the physical page is mapped.
45   uint64_t page_count = -1;
46   // Physical frame number of the page.
47   uint64_t pfn = -1;
48   // Number of zero bytes in the page.
49   uint64_t zero_bytes_count = -1;
50   // Memory region of the page.
51   const android::procinfo::MapInfo* mem_map = nullptr;
52 };
53 
54 struct ProcPages {
55   // Memory maps of the process.
56   std::vector<android::procinfo::MapInfo> maps;
57   // Page contents hash -> PageInfo
58   std::unordered_map<size_t, std::vector<PageInfo>> pages;
59 };
60 
ReadProcessPages(std::ostream & os,pid_t pid,ProcPages & proc_pages,ProcFiles & proc_files,uint64_t page_size)61 bool ReadProcessPages(
62     std::ostream& os, pid_t pid, ProcPages& proc_pages, ProcFiles& proc_files, uint64_t page_size) {
63   if (!android::procinfo::ReadProcessMaps(pid, &proc_pages.maps)) {
64     os << "Could not read process maps for " << pid;
65     return false;
66   }
67 
68   std::string error_msg;
69   std::vector<uint8_t> page_contents(page_size);
70   for (const android::procinfo::MapInfo& map_info : proc_pages.maps) {
71     for (uint64_t start = map_info.start; start < map_info.end; start += page_size) {
72       PageInfo page_info;
73       page_info.start = start;
74       page_info.end = start + page_size;
75       page_info.mem_map = &map_info;
76       const size_t virtual_page_index = start / page_size;
77       if (!GetPageFrameNumber(proc_files.pagemap, virtual_page_index, page_info.pfn, error_msg)) {
78         os << error_msg;
79         return false;
80       }
81       if (!GetPageFlagsOrCounts(proc_files.kpagecount,
82                                 ArrayRef<const uint64_t>(&page_info.pfn, 1),
83                                 /*out*/ ArrayRef<uint64_t>(&page_info.page_count, 1),
84                                 /*out*/ error_msg)) {
85         os << error_msg << std::endl;
86         os << "mem_map name: " << map_info.name << std::endl;
87         os << "pfn: " << page_info.pfn << std::endl;
88         os << "page_start: " << page_info.start << std::endl;
89         os << "mem_map start: " << map_info.start << std::endl;
90         // start = map_info.end;
91         continue;
92       }
93 
94       if (page_info.page_count == 0) {
95         // Page is not present in memory.
96         continue;
97       }
98 
99       // Handle present page.
100       if (!proc_files.mem.PreadFully(page_contents.data(), page_contents.size(), start)) {
101         os << StringPrintf("Failed to read present page %" PRIx64 " for mem_map %s\n",
102                            start,
103                            map_info.name.c_str());
104         return false;
105       }
106 
107       page_info.zero_bytes_count =
108           static_cast<uint64_t>(std::count(page_contents.begin(), page_contents.end(), 0));
109 
110       const size_t content_hash = DataHash::HashBytes(page_contents.data(), page_contents.size());
111       proc_pages.pages[content_hash].push_back(page_info);
112     }
113   }
114 
115   return true;
116 }
117 
FindUnsharedPages(std::ostream & os,pid_t pid1,pid_t pid2,size_t page_size)118 int FindUnsharedPages(std::ostream& os, pid_t pid1, pid_t pid2, size_t page_size) {
119   ProcFiles proc_files1;
120   ProcFiles proc_files2;
121   std::string error_msg;
122   if (!OpenProcFiles(pid1, proc_files1, error_msg)) {
123     os << error_msg;
124     return EXIT_FAILURE;
125   }
126   if (!OpenProcFiles(pid2, proc_files2, error_msg)) {
127     os << error_msg;
128     return EXIT_FAILURE;
129   }
130 
131   ProcPages proc_pages1;
132   if (!ReadProcessPages(os, pid1, proc_pages1, proc_files1, page_size)) {
133     return EXIT_FAILURE;
134   }
135 
136   ProcPages proc_pages2;
137   if (!ReadProcessPages(os, pid2, proc_pages2, proc_files2, page_size)) {
138     return EXIT_FAILURE;
139   }
140 
141   for (const auto& [hash, pages1] : proc_pages1.pages) {
142     // Skip zero pages.
143     if (pages1.front().zero_bytes_count == page_size) {
144       continue;
145     }
146 
147     // Find pages with the same content in the second process.
148     if (proc_pages2.pages.find(hash) != proc_pages2.pages.end()) {
149       const auto& pages2 = proc_pages2.pages[hash];
150 
151       std::unordered_set<uint64_t> pfns1, pfns2;
152       for (const auto& p : pages1) {
153         pfns1.insert(p.pfn);
154       }
155       for (const auto& p : pages2) {
156         pfns2.insert(p.pfn);
157       }
158 
159       const bool is_different = (pfns1 != pfns2);
160       if (is_different) {
161         os << "\nDuplicate pages (pfn, start_addr, mem_map, zero_bytes_count)\nPID1:\n";
162         for (const auto& page1 : pages1) {
163           os << ART_FORMAT("\t{} {} {} {}\n",
164                            page1.pfn,
165                            page1.start,
166                            page1.mem_map->name,
167                            page1.zero_bytes_count);
168         }
169         os << "PID2:\n";
170         for (const auto& page2 : pages2) {
171           os << ART_FORMAT("\t{} {} {} {}\n",
172                            page2.pfn,
173                            page2.start,
174                            page2.mem_map->name,
175                            page2.zero_bytes_count);
176         }
177       }
178     }
179   }
180 
181   return EXIT_SUCCESS;
182 }
183 
184 struct FindUnsharedPagesArgs : public CmdlineArgs {
185  protected:
186   using Base = CmdlineArgs;
187 
ParseCustomart::FindUnsharedPagesArgs188   ParseStatus ParseCustom(const char* raw_option,
189                           size_t raw_option_length,
190                           std::string* error_msg) override {
191     DCHECK_EQ(strlen(raw_option), raw_option_length);
192     {
193       ParseStatus base_parse = Base::ParseCustom(raw_option, raw_option_length, error_msg);
194       if (base_parse != kParseUnknownArgument) {
195         return base_parse;
196       }
197     }
198 
199     std::string_view option(raw_option, raw_option_length);
200     if (option.starts_with("--pid1=")) {
201       const char* value = raw_option + strlen("--pid1=");
202       if (!android::base::ParseInt(value, &pid1_)) {
203         *error_msg = "Failed to parse pid1";
204         return kParseError;
205       }
206     } else if (option.starts_with("--pid2=")) {
207       const char* value = raw_option + strlen("--pid2=");
208       if (!android::base::ParseInt(value, &pid2_)) {
209         *error_msg = "Failed to parse pid2";
210         return kParseError;
211       }
212     } else {
213       return kParseUnknownArgument;
214     }
215 
216     return kParseOk;
217   }
218 
ParseChecksart::FindUnsharedPagesArgs219   ParseStatus ParseChecks(std::string* error_msg) override {
220     ParseStatus parent_checks = Base::ParseChecks(error_msg);
221     if (parent_checks != kParseOk) {
222       return parent_checks;
223     }
224 
225     if (pid1_ == -1 || pid2_ == -1) {
226       *error_msg = "Missing --pid=";
227       return kParseError;
228     }
229 
230     for (pid_t pid : {pid1_, pid2_}) {
231       if (kill(pid, /*sig*/ 0) != 0) {  // No signal is sent, perform error-checking only.
232         // Check if the pid exists before proceeding.
233         if (errno == ESRCH) {
234           *error_msg = "Process specified does not exist, pid: " + std::to_string(pid);
235         } else {
236           *error_msg = StringPrintf("Failed to check process status: %s", strerror(errno));
237         }
238         return kParseError;
239       }
240     }
241     return kParseOk;
242   }
243 
GetUsageart::FindUnsharedPagesArgs244   std::string GetUsage() const override {
245     std::string usage;
246 
247     usage +=
248         "Usage: find_unshared_pages [options] ...\n"
249         "    Example: find_unshared_pages --pid1=$(pidof system_server) --pid2=$(pidof "
250         "com.android.camera2)\n"
251         "\n";
252 
253     usage += Base::GetUsage();
254 
255     usage += "  --pid1=<pid> --pid2=<pid>: PIDs of the processes to analyze.\n";
256 
257     return usage;
258   }
259 
260  public:
261   pid_t pid1_ = -1;
262   pid_t pid2_ = -1;
263 };
264 
265 struct FindUnsharedPagesMain : public CmdlineMain<FindUnsharedPagesArgs> {
ExecuteWithoutRuntimeart::FindUnsharedPagesMain266   bool ExecuteWithoutRuntime() override {
267     CHECK(args_ != nullptr);
268     CHECK(args_->os_ != nullptr);
269 
270     return FindUnsharedPages(*args_->os_, args_->pid1_, args_->pid2_, MemMap::GetPageSize()) ==
271            EXIT_SUCCESS;
272   }
273 
NeedsRuntimeart::FindUnsharedPagesMain274   bool NeedsRuntime() override { return false; }
275 };
276 
277 }  // namespace art
278 
main(int argc,char ** argv)279 int main(int argc, char** argv) {
280   art::FindUnsharedPagesMain main;
281   return main.Main(argc, argv);
282 }
283