1 /*
2 * Copyright (C) 2023 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 <functional>
24 #include <optional>
25 #include <ostream>
26 #include <string>
27 #include <vector>
28
29 #include "android-base/parseint.h"
30 #include "android-base/stringprintf.h"
31 #include "base/os.h"
32 #include "base/unix_file/fd_file.h"
33 #include "cmdline.h"
34 #include "page_util.h"
35 #include "procinfo/process_map.h"
36 #include "scoped_thread_state_change-inl.h"
37
38 namespace art {
39
40 using android::base::StringPrintf;
41
42 namespace {
43
DumpPageInfo(uint64_t virtual_page_index,ProcFiles & proc_files,std::ostream & os,size_t page_size)44 void DumpPageInfo(uint64_t virtual_page_index, ProcFiles& proc_files, std::ostream& os,
45 size_t page_size) {
46 const uint64_t virtual_page_addr = virtual_page_index * page_size;
47 os << "Virtual page index: " << virtual_page_index << "\n";
48 os << "Virtual page addr: " << virtual_page_addr << "\n";
49
50 std::string error_msg;
51 uint64_t page_frame_number = -1;
52 if (!GetPageFrameNumber(
53 proc_files.pagemap, virtual_page_index, /*out*/ page_frame_number, /*out*/ error_msg)) {
54 os << "Failed to get page frame number: " << error_msg << "\n";
55 return;
56 }
57 os << "Page frame number: " << page_frame_number << "\n";
58
59 uint64_t page_count = -1;
60 if (!GetPageFlagsOrCount(proc_files.kpagecount,
61 page_frame_number,
62 /*out*/ page_count,
63 /*out*/ error_msg)) {
64 os << "Failed to get page count: " << error_msg << "\n";
65 return;
66 }
67 os << "kpagecount: " << page_count << "\n";
68
69 uint64_t page_flags = 0;
70 if (!GetPageFlagsOrCount(proc_files.kpageflags,
71 page_frame_number,
72 /*out*/ page_flags,
73 /*out*/ error_msg)) {
74 os << "Failed to get page flags: " << error_msg << "\n";
75 return;
76 }
77 os << "kpageflags: " << page_flags << "\n";
78
79 if (page_count != 0) {
80 std::vector<uint8_t> page_contents(page_size);
81 if (!proc_files.mem.PreadFully(page_contents.data(), page_contents.size(), virtual_page_addr)) {
82 os << "Failed to read page contents\n";
83 return;
84 }
85 os << "Zero bytes: " << std::count(std::begin(page_contents), std::end(page_contents), 0)
86 << "\n";
87 }
88 }
89
90 struct MapPageCounts {
91 // Present pages count.
92 uint64_t pages = 0;
93 // Non-present pages count.
94 uint64_t non_present_pages = 0;
95 // Private (kpagecount == 1) zero page count.
96 uint64_t private_zero_pages = 0;
97 // Shared (kpagecount > 1) zero page count.
98 uint64_t shared_zero_pages = 0;
99 // Physical frame numbers of zero pages.
100 std::unordered_set<uint64_t> zero_page_pfns;
101
102 // Memory map name.
103 std::string name;
104 // Memory map start address.
105 uint64_t start = 0;
106 // Memory map end address.
107 uint64_t end = 0;
108 };
109
GetMapPageCounts(ProcFiles & proc_files,const android::procinfo::MapInfo & map_info,MapPageCounts & map_page_counts,std::string & error_msg,size_t page_size)110 bool GetMapPageCounts(ProcFiles& proc_files,
111 const android::procinfo::MapInfo& map_info,
112 MapPageCounts& map_page_counts,
113 std::string& error_msg,
114 size_t page_size) {
115 map_page_counts.name = map_info.name;
116 map_page_counts.start = map_info.start;
117 map_page_counts.end = map_info.end;
118 std::vector<uint8_t> page_contents(page_size);
119 for (uint64_t begin = map_info.start; begin < map_info.end; begin += page_size) {
120 const size_t virtual_page_index = begin / page_size;
121 uint64_t page_frame_number = -1;
122 if (!GetPageFrameNumber(proc_files.pagemap, virtual_page_index, page_frame_number, error_msg)) {
123 return false;
124 }
125 uint64_t page_count = -1;
126 if (!GetPageFlagsOrCounts(proc_files.kpagecount,
127 ArrayRef<const uint64_t>(&page_frame_number, 1),
128 /*out*/ ArrayRef<uint64_t>(&page_count, 1),
129 /*out*/ error_msg)) {
130 return false;
131 }
132
133 const auto is_zero_page = [](const std::vector<uint8_t>& page) {
134 const auto non_zero_it =
135 std::find_if(std::begin(page), std::end(page), [](uint8_t b) { return b != 0; });
136 return non_zero_it == std::end(page);
137 };
138
139 if (page_count == 0) {
140 map_page_counts.non_present_pages += 1;
141 continue;
142 }
143
144 // Handle present page.
145 if (!proc_files.mem.PreadFully(page_contents.data(), page_contents.size(), begin)) {
146 error_msg = StringPrintf(
147 "Failed to read present page %" PRIx64 " for mapping %s\n", begin, map_info.name.c_str());
148 return false;
149 }
150 const bool is_zero = is_zero_page(page_contents);
151 const bool is_private = (page_count == 1);
152 map_page_counts.pages += 1;
153 if (is_zero) {
154 map_page_counts.zero_page_pfns.insert(page_frame_number);
155 if (is_private) {
156 map_page_counts.private_zero_pages += 1;
157 } else {
158 map_page_counts.shared_zero_pages += 1;
159 }
160 }
161 }
162 return true;
163 }
164
CountZeroPages(pid_t pid,ProcFiles & proc_files,std::ostream & os,size_t page_size)165 void CountZeroPages(pid_t pid, ProcFiles& proc_files, std::ostream& os, size_t page_size) {
166 std::vector<android::procinfo::MapInfo> proc_maps;
167 if (!android::procinfo::ReadProcessMaps(pid, &proc_maps)) {
168 os << "Could not read process maps for " << pid;
169 return;
170 }
171
172 MapPageCounts total;
173 std::vector<MapPageCounts> stats;
174 for (const android::procinfo::MapInfo& map_info : proc_maps) {
175 MapPageCounts map_page_counts;
176 std::string error_msg;
177 if (!GetMapPageCounts(proc_files, map_info, map_page_counts, error_msg, page_size)) {
178 os << "Error getting map page counts for: " << map_info.name << "\n" << error_msg << "\n\n";
179 continue;
180 }
181 total.pages += map_page_counts.pages;
182 total.private_zero_pages += map_page_counts.private_zero_pages;
183 total.shared_zero_pages += map_page_counts.shared_zero_pages;
184 total.non_present_pages += map_page_counts.non_present_pages;
185 total.zero_page_pfns.insert(std::begin(map_page_counts.zero_page_pfns),
186 std::end(map_page_counts.zero_page_pfns));
187 stats.push_back(std::move(map_page_counts));
188 }
189
190 // Sort by different page counts, descending.
191 const auto sort_by_private_zero_pages = [](const auto& stats1, const auto& stats2) {
192 return stats1.private_zero_pages > stats2.private_zero_pages;
193 };
194 const auto sort_by_shared_zero_pages = [](const auto& stats1, const auto& stats2) {
195 return stats1.shared_zero_pages > stats2.shared_zero_pages;
196 };
197 const auto sort_by_unique_zero_pages = [](const auto& stats1, const auto& stats2) {
198 return stats1.zero_page_pfns.size() > stats2.zero_page_pfns.size();
199 };
200
201 // Print up to `max_lines` entries.
202 const auto print_stats = [&stats, &os](size_t max_lines) {
203 for (const MapPageCounts& map_page_counts : stats) {
204 if (max_lines == 0) {
205 return;
206 }
207 // Skip entries with no present pages.
208 if (map_page_counts.pages == 0) {
209 continue;
210 }
211 max_lines -= 1;
212 os << StringPrintf("%" PRIx64 "-%" PRIx64 " %s: pages=%" PRIu64
213 ", private_zero_pages=%" PRIu64 ", shared_zero_pages=%" PRIu64
214 ", unique_zero_pages=%" PRIu64 ", non_present_pages=%" PRIu64 "\n",
215 map_page_counts.start,
216 map_page_counts.end,
217 map_page_counts.name.c_str(),
218 map_page_counts.pages,
219 map_page_counts.private_zero_pages,
220 map_page_counts.shared_zero_pages,
221 uint64_t{map_page_counts.zero_page_pfns.size()},
222 map_page_counts.non_present_pages);
223 }
224 };
225
226 os << StringPrintf("total_pages=%" PRIu64 ", total_private_zero_pages=%" PRIu64
227 ", total_shared_zero_pages=%" PRIu64 ", total_unique_zero_pages=%" PRIu64
228 ", total_non_present_pages=%" PRIu64 "\n",
229 total.pages,
230 total.private_zero_pages,
231 total.shared_zero_pages,
232 uint64_t{total.zero_page_pfns.size()},
233 total.non_present_pages);
234 os << "\n\n";
235
236 const size_t top_lines = std::min(size_t{20}, stats.size());
237 std::partial_sort(
238 std::begin(stats), std::begin(stats) + top_lines, std::end(stats), sort_by_unique_zero_pages);
239 os << "Top " << top_lines << " maps by unique zero pages (unique PFN count)\n";
240 print_stats(top_lines);
241 os << "\n\n";
242
243 std::partial_sort(std::begin(stats),
244 std::begin(stats) + top_lines,
245 std::end(stats),
246 sort_by_private_zero_pages);
247 os << "Top " << top_lines << " maps by private zero pages (kpagecount == 1)\n";
248 print_stats(top_lines);
249 os << "\n\n";
250
251 std::partial_sort(
252 std::begin(stats), std::begin(stats) + top_lines, std::end(stats), sort_by_shared_zero_pages);
253 os << "Top " << top_lines << " maps by shared zero pages (kpagecount > 1)\n";
254 print_stats(top_lines);
255 os << "\n\n";
256
257 std::sort(std::begin(stats), std::end(stats), sort_by_unique_zero_pages);
258 os << "All maps by unique zero pages (unique PFN count)\n";
259 print_stats(stats.size());
260 os << "\n\n";
261 }
262
263 } // namespace
264
PageInfo(std::ostream & os,pid_t pid,bool count_zero_pages,std::optional<uint64_t> virtual_page_index,size_t page_size)265 int PageInfo(std::ostream& os,
266 pid_t pid,
267 bool count_zero_pages,
268 std::optional<uint64_t> virtual_page_index,
269 size_t page_size) {
270 ProcFiles proc_files;
271 std::string error_msg;
272 if (!OpenProcFiles(pid, proc_files, error_msg)) {
273 os << error_msg;
274 return EXIT_FAILURE;
275 }
276 if (virtual_page_index != std::nullopt) {
277 DumpPageInfo(virtual_page_index.value(), proc_files, os, page_size);
278 }
279 if (count_zero_pages) {
280 CountZeroPages(pid, proc_files, os, page_size);
281 }
282 return EXIT_SUCCESS;
283 }
284
285 struct PageInfoArgs : public CmdlineArgs {
286 protected:
287 using Base = CmdlineArgs;
288
ParseCustomart::PageInfoArgs289 ParseStatus ParseCustom(const char* raw_option,
290 size_t raw_option_length,
291 std::string* error_msg) override {
292 DCHECK_EQ(strlen(raw_option), raw_option_length);
293 {
294 ParseStatus base_parse = Base::ParseCustom(raw_option, raw_option_length, error_msg);
295 if (base_parse != kParseUnknownArgument) {
296 return base_parse;
297 }
298 }
299
300 std::string_view option(raw_option, raw_option_length);
301 if (option.starts_with("--pid=")) {
302 // static_assert(std::is_signed_t
303 const char* value = raw_option + strlen("--pid=");
304 if (!android::base::ParseInt(value, &pid_)) {
305 *error_msg = "Failed to parse pid";
306 return kParseError;
307 }
308 } else if (option == "--count-zero-pages") {
309 count_zero_pages_ = true;
310 } else if (option.starts_with("--dump-page-info=")) {
311 const char* value = raw_option + strlen("--dump-page-info=");
312 virtual_page_index_ = 0;
313 if (!android::base::ParseUint(value, &virtual_page_index_.value())) {
314 *error_msg = "Failed to parse virtual page index";
315 return kParseError;
316 }
317 } else {
318 return kParseUnknownArgument;
319 }
320
321 return kParseOk;
322 }
323
ParseChecksart::PageInfoArgs324 ParseStatus ParseChecks(std::string* error_msg) override {
325 // Perform the parent checks.
326 ParseStatus parent_checks = Base::ParseChecks(error_msg);
327 if (parent_checks != kParseOk) {
328 return parent_checks;
329 }
330 if (pid_ == -1) {
331 *error_msg = "Missing --pid=";
332 return kParseError;
333 }
334
335 // Perform our own checks.
336 if (kill(pid_, /*sig*/ 0) != 0) { // No signal is sent, perform error-checking only.
337 // Check if the pid exists before proceeding.
338 if (errno == ESRCH) {
339 *error_msg = "Process specified does not exist, pid: " + std::to_string(pid_);
340 } else {
341 *error_msg = StringPrintf("Failed to check process status: %s", strerror(errno));
342 }
343 return kParseError;
344 }
345 return kParseOk;
346 }
347
GetUsageart::PageInfoArgs348 std::string GetUsage() const override {
349 std::string usage;
350
351 usage +=
352 "Usage: pageinfo [options] ...\n"
353 " Example: pageinfo --pid=$(pidof system_server) --count-zero-pages\n"
354 " Example: adb shell pageinfo --pid=$(pid system_server) --dump-page-info=0x70000000\n"
355 "\n";
356
357 usage += Base::GetUsage();
358
359 usage +=
360 " --pid=<pid>: PID of the process to analyze.\n"
361 " --count-zero-pages: output zero filled page stats for memory mappings of "
362 "<image-diff-pid> process.\n"
363 " --dump-page-info=<virtual_page_index>: output PFN, kpagecount and kpageflags of a "
364 "virtual page in <image-diff-pid> process memory space.\n";
365
366 return usage;
367 }
368
369 public:
370 pid_t pid_ = -1;
371 bool count_zero_pages_ = false;
372 std::optional<uint64_t> virtual_page_index_;
373 };
374
375 struct PageInfoMain : public CmdlineMain<PageInfoArgs> {
ExecuteWithoutRuntimeart::PageInfoMain376 bool ExecuteWithoutRuntime() override {
377 CHECK(args_ != nullptr);
378 CHECK(args_->os_ != nullptr);
379
380 return PageInfo(
381 *args_->os_, args_->pid_, args_->count_zero_pages_, args_->virtual_page_index_,
382 MemMap::GetPageSize()) ==
383 EXIT_SUCCESS;
384 }
385
NeedsRuntimeart::PageInfoMain386 bool NeedsRuntime() override { return false; }
387 };
388
389 } // namespace art
390
main(int argc,char ** argv)391 int main(int argc, char** argv) {
392 art::PageInfoMain main;
393 return main.Main(argc, argv);
394 }
395