1 /*
2  * Copyright (C) 2017 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 <errno.h>
18 #include <inttypes.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include <iostream>
24 #include <memory>
25 #include <string>
26 #include <string_view>
27 #include <vector>
28 
29 #include "android-base/stringprintf.h"
30 
31 #include "base/logging.h"  // For InitLogging.
32 #include "base/string_view_cpp20.h"
33 
34 #include "dexlayout.h"
35 #include "dex/dex_file.h"
36 #include "dex_ir.h"
37 #include "dex_ir_builder.h"
38 #ifdef ART_TARGET_ANDROID
39 #include <meminfo/pageacct.h>
40 #include <meminfo/procmeminfo.h>
41 #endif
42 #include "vdex_file.h"
43 
44 namespace art {
45 
46 using android::base::StringPrintf;
47 #ifdef ART_TARGET_ANDROID
48 using android::meminfo::ProcMemInfo;
49 using android::meminfo::Vma;
50 #endif
51 
52 static bool g_verbose = false;
53 
54 // The width needed to print a file page offset (32-bit).
55 static constexpr int kPageCountWidth =
56     static_cast<int>(std::numeric_limits<uint32_t>::digits10);
57 // Display the sections.
58 static constexpr char kSectionHeader[] = "Section name";
59 
60 struct DexSectionInfo {
61  public:
62   std::string name;
63   char letter;
64 };
65 
66 static const std::map<uint16_t, DexSectionInfo> kDexSectionInfoMap = {
67   { DexFile::kDexTypeHeaderItem, { "Header", 'H' } },
68   { DexFile::kDexTypeStringIdItem, { "StringId", 'S' } },
69   { DexFile::kDexTypeTypeIdItem, { "TypeId", 'T' } },
70   { DexFile::kDexTypeProtoIdItem, { "ProtoId", 'P' } },
71   { DexFile::kDexTypeFieldIdItem, { "FieldId", 'F' } },
72   { DexFile::kDexTypeMethodIdItem, { "MethodId", 'M' } },
73   { DexFile::kDexTypeClassDefItem, { "ClassDef", 'C' } },
74   { DexFile::kDexTypeCallSiteIdItem, { "CallSiteId", 'z' } },
75   { DexFile::kDexTypeMethodHandleItem, { "MethodHandle", 'Z' } },
76   { DexFile::kDexTypeMapList, { "TypeMap", 'L' } },
77   { DexFile::kDexTypeTypeList, { "TypeList", 't' } },
78   { DexFile::kDexTypeAnnotationSetRefList, { "AnnotationSetReferenceItem", '1' } },
79   { DexFile::kDexTypeAnnotationSetItem, { "AnnotationSetItem", '2' } },
80   { DexFile::kDexTypeClassDataItem, { "ClassData", 'c' } },
81   { DexFile::kDexTypeCodeItem, { "CodeItem", 'X' } },
82   { DexFile::kDexTypeStringDataItem, { "StringData", 's' } },
83   { DexFile::kDexTypeDebugInfoItem, { "DebugInfo", 'D' } },
84   { DexFile::kDexTypeAnnotationItem, { "AnnotationItem", '3' } },
85   { DexFile::kDexTypeEncodedArrayItem, { "EncodedArrayItem", 'E' } },
86   { DexFile::kDexTypeAnnotationsDirectoryItem, { "AnnotationsDirectoryItem", '4' } }
87 };
88 
89 class PageCount {
90  public:
PageCount()91   PageCount() {
92     for (auto it = kDexSectionInfoMap.begin(); it != kDexSectionInfoMap.end(); ++it) {
93       map_[it->first] = 0;
94     }
95   }
Increment(uint16_t type)96   void Increment(uint16_t type) {
97     map_[type]++;
98   }
Get(uint16_t type) const99   size_t Get(uint16_t type) const {
100     auto it = map_.find(type);
101     DCHECK(it != map_.end());
102     return it->second;
103   }
104  private:
105   std::map<uint16_t, size_t> map_;
106   DISALLOW_COPY_AND_ASSIGN(PageCount);
107 };
108 
109 class Printer {
110  public:
Printer()111   Printer() : section_header_width_(ComputeHeaderWidth()) {
112   }
113 
PrintHeader() const114   void PrintHeader() const {
115     std::cout << StringPrintf("%-*s %*s %*s %% of   %% of",
116                               section_header_width_,
117                               kSectionHeader,
118                               kPageCountWidth,
119                               "resident",
120                               kPageCountWidth,
121                               "total"
122                               )
123               << std::endl;
124     std::cout << StringPrintf("%-*s %*s %*s sect.  total",
125                               section_header_width_,
126                               "",
127                               kPageCountWidth,
128                               "pages",
129                               kPageCountWidth,
130                               "pages")
131               << std::endl;
132   }
133 
PrintOne(const char * name,size_t resident,size_t mapped,double percent_of_section,double percent_of_total) const134   void PrintOne(const char* name,
135                 size_t resident,
136                 size_t mapped,
137                 double percent_of_section,
138                 double percent_of_total) const {
139     // 6.2 is sufficient to print 0-100% with two decimal places of accuracy.
140     std::cout << StringPrintf("%-*s %*zd %*zd %6.2f %6.2f",
141                               section_header_width_,
142                               name,
143                               kPageCountWidth,
144                               resident,
145                               kPageCountWidth,
146                               mapped,
147                               percent_of_section,
148                               percent_of_total)
149               << std::endl;
150   }
151 
PrintSkipLine() const152   void PrintSkipLine() const { std::cout << std::endl; }
153 
154   // Computes the width of the section header column in the table (for fixed formatting).
ComputeHeaderWidth()155   static int ComputeHeaderWidth() {
156     int header_width = 0;
157     for (const auto& pair : kDexSectionInfoMap) {
158       const DexSectionInfo& section_info = pair.second;
159       header_width = std::max(header_width, static_cast<int>(section_info.name.length()));
160     }
161     return header_width;
162   }
163 
164  private:
165   const int section_header_width_;
166 };
167 
PrintLetterKey()168 static void PrintLetterKey() {
169   std::cout << "L pagetype" << std::endl;
170   for (const auto& pair : kDexSectionInfoMap) {
171     const DexSectionInfo& section_info = pair.second;
172     std::cout << section_info.letter << " " << section_info.name.c_str() << std::endl;
173   }
174   std::cout << "* (Executable page resident)" << std::endl;
175   std::cout << ". (Mapped page not resident)" << std::endl;
176 }
177 
178 #ifdef ART_TARGET_ANDROID
PageTypeChar(uint16_t type)179 static char PageTypeChar(uint16_t type) {
180   if (kDexSectionInfoMap.find(type) == kDexSectionInfoMap.end()) {
181     return '-';
182   }
183   return kDexSectionInfoMap.find(type)->second.letter;
184 }
185 
FindSectionTypeForPage(size_t page,const std::vector<dex_ir::DexFileSection> & sections)186 static uint16_t FindSectionTypeForPage(size_t page,
187                                        const std::vector<dex_ir::DexFileSection>& sections) {
188   for (const auto& section : sections) {
189     size_t first_page_of_section = section.offset / kPageSize;
190     // Only consider non-empty sections.
191     if (section.size == 0) {
192       continue;
193     }
194     // Attribute the page to the highest-offset section that starts before the page.
195     if (first_page_of_section <= page) {
196       return section.type;
197     }
198   }
199   // If there's no non-zero sized section with an offset below offset we're looking for, it
200   // must be the header.
201   return DexFile::kDexTypeHeaderItem;
202 }
203 
ProcessPageMap(const std::vector<uint64_t> & pagemap,size_t start,size_t end,const std::vector<dex_ir::DexFileSection> & sections,PageCount * page_counts)204 static void ProcessPageMap(const std::vector<uint64_t>& pagemap,
205                            size_t start,
206                            size_t end,
207                            const std::vector<dex_ir::DexFileSection>& sections,
208                            PageCount* page_counts) {
209   static constexpr size_t kLineLength = 32;
210   for (size_t page = start; page < end; ++page) {
211     char type_char = '.';
212     if (::android::meminfo::page_present(pagemap[page])) {
213       const size_t dex_page_offset = page - start;
214       uint16_t type = FindSectionTypeForPage(dex_page_offset, sections);
215       page_counts->Increment(type);
216       type_char = PageTypeChar(type);
217     }
218     if (g_verbose) {
219       std::cout << type_char;
220       if ((page - start) % kLineLength == kLineLength - 1) {
221         std::cout << std::endl;
222       }
223     }
224   }
225   if (g_verbose) {
226     if ((end - start) % kLineLength != 0) {
227       std::cout << std::endl;
228     }
229   }
230 }
231 
DisplayDexStatistics(size_t start,size_t end,const PageCount & resident_pages,const std::vector<dex_ir::DexFileSection> & sections,Printer * printer)232 static void DisplayDexStatistics(size_t start,
233                                  size_t end,
234                                  const PageCount& resident_pages,
235                                  const std::vector<dex_ir::DexFileSection>& sections,
236                                  Printer* printer) {
237   // Compute the total possible sizes for sections.
238   PageCount mapped_pages;
239   DCHECK_GE(end, start);
240   size_t total_mapped_pages = end - start;
241   if (total_mapped_pages == 0) {
242     return;
243   }
244   for (size_t page = start; page < end; ++page) {
245     const size_t dex_page_offset = page - start;
246     mapped_pages.Increment(FindSectionTypeForPage(dex_page_offset, sections));
247   }
248   size_t total_resident_pages = 0;
249   printer->PrintHeader();
250   for (size_t i = sections.size(); i > 0; --i) {
251     const dex_ir::DexFileSection& section = sections[i - 1];
252     const uint16_t type = section.type;
253     const DexSectionInfo& section_info = kDexSectionInfoMap.find(type)->second;
254     size_t pages_resident = resident_pages.Get(type);
255     double percent_resident = 0;
256     if (mapped_pages.Get(type) > 0) {
257       percent_resident = 100.0 * pages_resident / mapped_pages.Get(type);
258     }
259     printer->PrintOne(section_info.name.c_str(),
260                       pages_resident,
261                       mapped_pages.Get(type),
262                       percent_resident,
263                       100.0 * pages_resident / total_mapped_pages);
264     total_resident_pages += pages_resident;
265   }
266   double percent_of_total = 100.0 * total_resident_pages / total_mapped_pages;
267   printer->PrintOne("GRAND TOTAL",
268                     total_resident_pages,
269                     total_mapped_pages,
270                     percent_of_total,
271                     percent_of_total);
272   printer->PrintSkipLine();
273 }
274 
ProcessOneDexMapping(const std::vector<uint64_t> & pagemap,uint64_t map_start,const DexFile * dex_file,uint64_t vdex_start,Printer * printer)275 static void ProcessOneDexMapping(const std::vector<uint64_t>& pagemap,
276                                  uint64_t map_start,
277                                  const DexFile* dex_file,
278                                  uint64_t vdex_start,
279                                  Printer* printer) {
280   uint64_t dex_file_start = reinterpret_cast<uint64_t>(dex_file->Begin());
281   size_t dex_file_size = dex_file->Size();
282   if (dex_file_start < vdex_start) {
283     std::cerr << "Dex file start offset for "
284               << dex_file->GetLocation().c_str()
285               << " is incorrect: map start "
286               << StringPrintf("%" PRIx64 " > dex start %" PRIx64 "\n", map_start, dex_file_start)
287               << std::endl;
288     return;
289   }
290   uint64_t start_page = (dex_file_start - vdex_start) / kPageSize;
291   uint64_t start_address = start_page * kPageSize;
292   uint64_t end_page = RoundUp(start_address + dex_file_size, kPageSize) / kPageSize;
293   std::cout << "DEX "
294             << dex_file->GetLocation().c_str()
295             << StringPrintf(": %" PRIx64 "-%" PRIx64,
296                             map_start + start_page * kPageSize,
297                             map_start + end_page * kPageSize)
298             << std::endl;
299   // Build a list of the dex file section types, sorted from highest offset to lowest.
300   std::vector<dex_ir::DexFileSection> sections;
301   {
302     Options options;
303     std::unique_ptr<dex_ir::Header> header(dex_ir::DexIrBuilder(*dex_file,
304                                                                 /*eagerly_assign_offsets=*/ true,
305                                                                 options));
306     sections = dex_ir::GetSortedDexFileSections(header.get(),
307                                                 dex_ir::SortDirection::kSortDescending);
308   }
309   PageCount section_resident_pages;
310   ProcessPageMap(pagemap, start_page, end_page, sections, &section_resident_pages);
311   DisplayDexStatistics(start_page, end_page, section_resident_pages, sections, printer);
312 }
313 
IsVdexFileMapping(const std::string & mapped_name)314 static bool IsVdexFileMapping(const std::string& mapped_name) {
315   // Confirm that the map is from a vdex file.
316   static const char* suffixes[] = { ".vdex" };
317   for (const char* suffix : suffixes) {
318     size_t match_loc = mapped_name.find(suffix);
319     if (match_loc != std::string::npos && mapped_name.length() == match_loc + strlen(suffix)) {
320       return true;
321     }
322   }
323   return false;
324 }
325 
DisplayMappingIfFromVdexFile(ProcMemInfo & proc,const Vma & vma,Printer * printer)326 static bool DisplayMappingIfFromVdexFile(ProcMemInfo& proc, const Vma& vma, Printer* printer) {
327   std::string vdex_name = vma.name;
328   // Extract all the dex files from the vdex file.
329   std::string error_msg;
330   std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_name,
331                                                 /*writable=*/ false,
332                                                 /*low_4gb=*/ false,
333                                                 /*unquicken= */ false,
334                                                 &error_msg /*out*/));
335   if (vdex == nullptr) {
336     std::cerr << "Could not open vdex file "
337               << vdex_name
338               << ": error "
339               << error_msg
340               << std::endl;
341     return false;
342   }
343 
344   std::vector<std::unique_ptr<const DexFile>> dex_files;
345   if (!vdex->OpenAllDexFiles(&dex_files, &error_msg)) {
346     std::cerr << "Dex files could not be opened for "
347               << vdex_name
348               << ": error "
349               << error_msg
350               << std::endl;
351     return false;
352   }
353   // Open the page mapping (one uint64_t per page) for the entire vdex mapping.
354   std::vector<uint64_t> pagemap;
355   if (!proc.PageMap(vma, &pagemap)) {
356     std::cerr << "Error creating pagemap." << std::endl;
357     return false;
358   }
359   // Process the dex files.
360   std::cout << "MAPPING "
361             << vma.name
362             << StringPrintf(": %" PRIx64 "-%" PRIx64, vma.start, vma.end)
363             << std::endl;
364   for (const auto& dex_file : dex_files) {
365     ProcessOneDexMapping(pagemap,
366                          vma.start,
367                          dex_file.get(),
368                          reinterpret_cast<uint64_t>(vdex->Begin()),
369                          printer);
370   }
371   return true;
372 }
373 
ProcessOneOatMapping(const std::vector<uint64_t> & pagemap,Printer * printer)374 static void ProcessOneOatMapping(const std::vector<uint64_t>& pagemap,
375                                  Printer* printer) {
376   static constexpr size_t kLineLength = 32;
377   size_t resident_page_count = 0;
378   for (size_t page = 0; page < pagemap.size(); ++page) {
379     char type_char = '.';
380     if (::android::meminfo::page_present(pagemap[page])) {
381       ++resident_page_count;
382       type_char = '*';
383     }
384     if (g_verbose) {
385       std::cout << type_char;
386       if (page % kLineLength == kLineLength - 1) {
387         std::cout << std::endl;
388       }
389     }
390   }
391   if (g_verbose) {
392     if (pagemap.size() % kLineLength != 0) {
393       std::cout << std::endl;
394     }
395   }
396   double percent_of_total = 100.0 * resident_page_count / pagemap.size();
397   printer->PrintHeader();
398   printer->PrintOne("EXECUTABLE", resident_page_count, pagemap.size(), percent_of_total, percent_of_total);
399   printer->PrintSkipLine();
400 }
401 
IsOatFileMapping(const std::string & mapped_name)402 static bool IsOatFileMapping(const std::string& mapped_name) {
403   // Confirm that the map is from an oat file.
404   static const char* suffixes[] = { ".odex", ".oat" };
405   for (const char* suffix : suffixes) {
406     size_t match_loc = mapped_name.find(suffix);
407     if (match_loc != std::string::npos && mapped_name.length() == match_loc + strlen(suffix)) {
408       return true;
409     }
410   }
411   return false;
412 }
413 
DisplayMappingIfFromOatFile(ProcMemInfo & proc,const Vma & vma,Printer * printer)414 static bool DisplayMappingIfFromOatFile(ProcMemInfo& proc, const Vma& vma, Printer* printer) {
415   // Open the page mapping (one uint64_t per page) for the entire vdex mapping.
416   std::vector<uint64_t> pagemap;
417   if (!proc.PageMap(vma, &pagemap) != 0) {
418     std::cerr << "Error creating pagemap." << std::endl;
419     return false;
420   }
421   // Process the dex files.
422   std::cout << "MAPPING "
423             << vma.name
424             << StringPrintf(": %" PRIx64 "-%" PRIx64, vma.start, vma.end)
425             << std::endl;
426   ProcessOneOatMapping(pagemap, printer);
427   return true;
428 }
429 
FilterByNameContains(const std::string & mapped_file_name,const std::vector<std::string> & name_filters)430 static bool FilterByNameContains(const std::string& mapped_file_name,
431                                  const std::vector<std::string>& name_filters) {
432   // If no filters were set, everything matches.
433   if (name_filters.empty()) {
434     return true;
435   }
436   for (const auto& name_contains : name_filters) {
437     if (mapped_file_name.find(name_contains) != std::string::npos) {
438       return true;
439     }
440   }
441   return false;
442 }
443 #endif
444 
Usage(const char * cmd)445 static void Usage(const char* cmd) {
446   std::cout << "Usage: " << cmd << " [options] pid" << std::endl
447             << "    --contains=<string>:  Display sections containing string." << std::endl
448             << "    --help:               Shows this message." << std::endl
449             << "    --verbose:            Makes displays verbose." << std::endl;
450   PrintLetterKey();
451 }
452 
Abort(const char * msg)453 NO_RETURN static void Abort(const char* msg) {
454   std::cerr << msg;
455   exit(1);
456 }
457 
DexDiagMain(int argc,char * argv[])458 static int DexDiagMain(int argc, char* argv[]) {
459   if (argc < 2) {
460     Usage(argv[0]);
461     return EXIT_FAILURE;
462   }
463 
464   std::vector<std::string> name_filters;
465   // TODO: add option to track usage by class name, etc.
466   for (int i = 1; i < argc - 1; ++i) {
467     const std::string_view option(argv[i]);
468     if (option == "--help") {
469       Usage(argv[0]);
470       return EXIT_SUCCESS;
471     } else if (option == "--verbose") {
472       g_verbose = true;
473     } else if (StartsWith(option, "--contains=")) {
474       std::string contains(option.substr(strlen("--contains=")));
475       name_filters.push_back(contains);
476     } else {
477       Usage(argv[0]);
478       return EXIT_FAILURE;
479     }
480   }
481 
482   // Art specific set up.
483   InitLogging(argv, Abort);
484   MemMap::Init();
485 
486 #ifdef ART_TARGET_ANDROID
487   pid_t pid;
488   char* endptr;
489   pid = (pid_t)strtol(argv[argc - 1], &endptr, 10);
490   if (*endptr != '\0' || kill(pid, 0) != 0) {
491     std::cerr << StringPrintf("Invalid PID \"%s\".\n", argv[argc - 1]) << std::endl;
492     return EXIT_FAILURE;
493   }
494 
495   // get libmeminfo process information.
496   ProcMemInfo proc(pid);
497   // Get the set of mappings by the specified process.
498   // Do not get the map usage stats, they are never used and it can take
499   // a long time to get this data.
500   const std::vector<Vma>& maps = proc.MapsWithoutUsageStats();
501   if (maps.empty()) {
502     std::cerr << "Error listing maps." << std::endl;
503     return EXIT_FAILURE;
504   }
505 
506   bool match_found = false;
507   // Process the mappings that are due to vdex or oat files.
508   Printer printer;
509   for (auto& vma : maps) {
510     std::string mapped_file_name = vma.name;
511     // Filter by name contains options (if any).
512     if (!FilterByNameContains(mapped_file_name, name_filters)) {
513       continue;
514     }
515     if (IsVdexFileMapping(mapped_file_name)) {
516       if (!DisplayMappingIfFromVdexFile(proc, vma, &printer)) {
517         return EXIT_FAILURE;
518       }
519       match_found = true;
520     } else if (IsOatFileMapping(mapped_file_name)) {
521       if (!DisplayMappingIfFromOatFile(proc, vma, &printer)) {
522         return EXIT_FAILURE;
523       }
524       match_found = true;
525     }
526   }
527   if (!match_found) {
528     std::cerr << "No relevant memory maps were found." << std::endl;
529     return EXIT_FAILURE;
530   }
531 #endif
532 
533   return EXIT_SUCCESS;
534 }
535 
536 }  // namespace art
537 
main(int argc,char * argv[])538 int main(int argc, char* argv[]) {
539   return art::DexDiagMain(argc, argv);
540 }
541