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