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