• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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