• 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 
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