/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include "android-base/stringprintf.h" #include "base/logging.h" // For InitLogging. #include "base/string_view_cpp20.h" #include "dexlayout.h" #include "dex/dex_file.h" #include "dex_ir.h" #include "dex_ir_builder.h" #ifdef ART_TARGET_ANDROID #include #include #endif #include "vdex_file.h" namespace art { using android::base::StringPrintf; #ifdef ART_TARGET_ANDROID using android::meminfo::ProcMemInfo; using android::meminfo::Vma; #endif static bool g_verbose = false; // The width needed to print a file page offset (32-bit). static constexpr int kPageCountWidth = static_cast(std::numeric_limits::digits10); // Display the sections. static constexpr char kSectionHeader[] = "Section name"; struct DexSectionInfo { public: std::string name; char letter; }; static const std::map kDexSectionInfoMap = { { DexFile::kDexTypeHeaderItem, { "Header", 'H' } }, { DexFile::kDexTypeStringIdItem, { "StringId", 'S' } }, { DexFile::kDexTypeTypeIdItem, { "TypeId", 'T' } }, { DexFile::kDexTypeProtoIdItem, { "ProtoId", 'P' } }, { DexFile::kDexTypeFieldIdItem, { "FieldId", 'F' } }, { DexFile::kDexTypeMethodIdItem, { "MethodId", 'M' } }, { DexFile::kDexTypeClassDefItem, { "ClassDef", 'C' } }, { DexFile::kDexTypeCallSiteIdItem, { "CallSiteId", 'z' } }, { DexFile::kDexTypeMethodHandleItem, { "MethodHandle", 'Z' } }, { DexFile::kDexTypeMapList, { "TypeMap", 'L' } }, { DexFile::kDexTypeTypeList, { "TypeList", 't' } }, { DexFile::kDexTypeAnnotationSetRefList, { "AnnotationSetReferenceItem", '1' } }, { DexFile::kDexTypeAnnotationSetItem, { "AnnotationSetItem", '2' } }, { DexFile::kDexTypeClassDataItem, { "ClassData", 'c' } }, { DexFile::kDexTypeCodeItem, { "CodeItem", 'X' } }, { DexFile::kDexTypeStringDataItem, { "StringData", 's' } }, { DexFile::kDexTypeDebugInfoItem, { "DebugInfo", 'D' } }, { DexFile::kDexTypeAnnotationItem, { "AnnotationItem", '3' } }, { DexFile::kDexTypeEncodedArrayItem, { "EncodedArrayItem", 'E' } }, { DexFile::kDexTypeAnnotationsDirectoryItem, { "AnnotationsDirectoryItem", '4' } } }; class PageCount { public: PageCount() { for (auto it = kDexSectionInfoMap.begin(); it != kDexSectionInfoMap.end(); ++it) { map_[it->first] = 0; } } void Increment(uint16_t type) { map_[type]++; } size_t Get(uint16_t type) const { auto it = map_.find(type); DCHECK(it != map_.end()); return it->second; } private: std::map map_; DISALLOW_COPY_AND_ASSIGN(PageCount); }; class Printer { public: Printer() : section_header_width_(ComputeHeaderWidth()) { } void PrintHeader() const { std::cout << StringPrintf("%-*s %*s %*s %% of %% of", section_header_width_, kSectionHeader, kPageCountWidth, "resident", kPageCountWidth, "total" ) << std::endl; std::cout << StringPrintf("%-*s %*s %*s sect. total", section_header_width_, "", kPageCountWidth, "pages", kPageCountWidth, "pages") << std::endl; } void PrintOne(const char* name, size_t resident, size_t mapped, double percent_of_section, double percent_of_total) const { // 6.2 is sufficient to print 0-100% with two decimal places of accuracy. std::cout << StringPrintf("%-*s %*zd %*zd %6.2f %6.2f", section_header_width_, name, kPageCountWidth, resident, kPageCountWidth, mapped, percent_of_section, percent_of_total) << std::endl; } void PrintSkipLine() const { std::cout << std::endl; } // Computes the width of the section header column in the table (for fixed formatting). static int ComputeHeaderWidth() { int header_width = 0; for (const auto& pair : kDexSectionInfoMap) { const DexSectionInfo& section_info = pair.second; header_width = std::max(header_width, static_cast(section_info.name.length())); } return header_width; } private: const int section_header_width_; }; static void PrintLetterKey() { std::cout << "L pagetype" << std::endl; for (const auto& pair : kDexSectionInfoMap) { const DexSectionInfo& section_info = pair.second; std::cout << section_info.letter << " " << section_info.name.c_str() << std::endl; } std::cout << "* (Executable page resident)" << std::endl; std::cout << ". (Mapped page not resident)" << std::endl; } #ifdef ART_TARGET_ANDROID static char PageTypeChar(uint16_t type) { if (kDexSectionInfoMap.find(type) == kDexSectionInfoMap.end()) { return '-'; } return kDexSectionInfoMap.find(type)->second.letter; } static uint16_t FindSectionTypeForPage(size_t page, const std::vector& sections) { for (const auto& section : sections) { size_t first_page_of_section = section.offset / kPageSize; // Only consider non-empty sections. if (section.size == 0) { continue; } // Attribute the page to the highest-offset section that starts before the page. if (first_page_of_section <= page) { return section.type; } } // If there's no non-zero sized section with an offset below offset we're looking for, it // must be the header. return DexFile::kDexTypeHeaderItem; } static void ProcessPageMap(const std::vector& pagemap, size_t start, size_t end, const std::vector& sections, PageCount* page_counts) { static constexpr size_t kLineLength = 32; for (size_t page = start; page < end; ++page) { char type_char = '.'; if (::android::meminfo::page_present(pagemap[page])) { const size_t dex_page_offset = page - start; uint16_t type = FindSectionTypeForPage(dex_page_offset, sections); page_counts->Increment(type); type_char = PageTypeChar(type); } if (g_verbose) { std::cout << type_char; if ((page - start) % kLineLength == kLineLength - 1) { std::cout << std::endl; } } } if (g_verbose) { if ((end - start) % kLineLength != 0) { std::cout << std::endl; } } } static void DisplayDexStatistics(size_t start, size_t end, const PageCount& resident_pages, const std::vector& sections, Printer* printer) { // Compute the total possible sizes for sections. PageCount mapped_pages; DCHECK_GE(end, start); size_t total_mapped_pages = end - start; if (total_mapped_pages == 0) { return; } for (size_t page = start; page < end; ++page) { const size_t dex_page_offset = page - start; mapped_pages.Increment(FindSectionTypeForPage(dex_page_offset, sections)); } size_t total_resident_pages = 0; printer->PrintHeader(); for (size_t i = sections.size(); i > 0; --i) { const dex_ir::DexFileSection& section = sections[i - 1]; const uint16_t type = section.type; const DexSectionInfo& section_info = kDexSectionInfoMap.find(type)->second; size_t pages_resident = resident_pages.Get(type); double percent_resident = 0; if (mapped_pages.Get(type) > 0) { percent_resident = 100.0 * pages_resident / mapped_pages.Get(type); } printer->PrintOne(section_info.name.c_str(), pages_resident, mapped_pages.Get(type), percent_resident, 100.0 * pages_resident / total_mapped_pages); total_resident_pages += pages_resident; } double percent_of_total = 100.0 * total_resident_pages / total_mapped_pages; printer->PrintOne("GRAND TOTAL", total_resident_pages, total_mapped_pages, percent_of_total, percent_of_total); printer->PrintSkipLine(); } static void ProcessOneDexMapping(const std::vector& pagemap, uint64_t map_start, const DexFile* dex_file, uint64_t vdex_start, Printer* printer) { uint64_t dex_file_start = reinterpret_cast(dex_file->Begin()); size_t dex_file_size = dex_file->Size(); if (dex_file_start < vdex_start) { std::cerr << "Dex file start offset for " << dex_file->GetLocation().c_str() << " is incorrect: map start " << StringPrintf("%" PRIx64 " > dex start %" PRIx64 "\n", map_start, dex_file_start) << std::endl; return; } uint64_t start_page = (dex_file_start - vdex_start) / kPageSize; uint64_t start_address = start_page * kPageSize; uint64_t end_page = RoundUp(start_address + dex_file_size, kPageSize) / kPageSize; std::cout << "DEX " << dex_file->GetLocation().c_str() << StringPrintf(": %" PRIx64 "-%" PRIx64, map_start + start_page * kPageSize, map_start + end_page * kPageSize) << std::endl; // Build a list of the dex file section types, sorted from highest offset to lowest. std::vector sections; { Options options; std::unique_ptr header(dex_ir::DexIrBuilder(*dex_file, /*eagerly_assign_offsets=*/ true, options)); sections = dex_ir::GetSortedDexFileSections(header.get(), dex_ir::SortDirection::kSortDescending); } PageCount section_resident_pages; ProcessPageMap(pagemap, start_page, end_page, sections, §ion_resident_pages); DisplayDexStatistics(start_page, end_page, section_resident_pages, sections, printer); } static bool IsVdexFileMapping(const std::string& mapped_name) { // Confirm that the map is from a vdex file. static const char* suffixes[] = { ".vdex" }; for (const char* suffix : suffixes) { size_t match_loc = mapped_name.find(suffix); if (match_loc != std::string::npos && mapped_name.length() == match_loc + strlen(suffix)) { return true; } } return false; } static bool DisplayMappingIfFromVdexFile(ProcMemInfo& proc, const Vma& vma, Printer* printer) { std::string vdex_name = vma.name; // Extract all the dex files from the vdex file. std::string error_msg; std::unique_ptr vdex(VdexFile::Open(vdex_name, /*writable=*/ false, /*low_4gb=*/ false, /*unquicken= */ false, &error_msg /*out*/)); if (vdex == nullptr) { std::cerr << "Could not open vdex file " << vdex_name << ": error " << error_msg << std::endl; return false; } std::vector> dex_files; if (!vdex->OpenAllDexFiles(&dex_files, &error_msg)) { std::cerr << "Dex files could not be opened for " << vdex_name << ": error " << error_msg << std::endl; return false; } // Open the page mapping (one uint64_t per page) for the entire vdex mapping. std::vector pagemap; if (!proc.PageMap(vma, &pagemap)) { std::cerr << "Error creating pagemap." << std::endl; return false; } // Process the dex files. std::cout << "MAPPING " << vma.name << StringPrintf(": %" PRIx64 "-%" PRIx64, vma.start, vma.end) << std::endl; for (const auto& dex_file : dex_files) { ProcessOneDexMapping(pagemap, vma.start, dex_file.get(), reinterpret_cast(vdex->Begin()), printer); } return true; } static void ProcessOneOatMapping(const std::vector& pagemap, Printer* printer) { static constexpr size_t kLineLength = 32; size_t resident_page_count = 0; for (size_t page = 0; page < pagemap.size(); ++page) { char type_char = '.'; if (::android::meminfo::page_present(pagemap[page])) { ++resident_page_count; type_char = '*'; } if (g_verbose) { std::cout << type_char; if (page % kLineLength == kLineLength - 1) { std::cout << std::endl; } } } if (g_verbose) { if (pagemap.size() % kLineLength != 0) { std::cout << std::endl; } } double percent_of_total = 100.0 * resident_page_count / pagemap.size(); printer->PrintHeader(); printer->PrintOne("EXECUTABLE", resident_page_count, pagemap.size(), percent_of_total, percent_of_total); printer->PrintSkipLine(); } static bool IsOatFileMapping(const std::string& mapped_name) { // Confirm that the map is from an oat file. static const char* suffixes[] = { ".odex", ".oat" }; for (const char* suffix : suffixes) { size_t match_loc = mapped_name.find(suffix); if (match_loc != std::string::npos && mapped_name.length() == match_loc + strlen(suffix)) { return true; } } return false; } static bool DisplayMappingIfFromOatFile(ProcMemInfo& proc, const Vma& vma, Printer* printer) { // Open the page mapping (one uint64_t per page) for the entire vdex mapping. std::vector pagemap; if (!proc.PageMap(vma, &pagemap) != 0) { std::cerr << "Error creating pagemap." << std::endl; return false; } // Process the dex files. std::cout << "MAPPING " << vma.name << StringPrintf(": %" PRIx64 "-%" PRIx64, vma.start, vma.end) << std::endl; ProcessOneOatMapping(pagemap, printer); return true; } static bool FilterByNameContains(const std::string& mapped_file_name, const std::vector& name_filters) { // If no filters were set, everything matches. if (name_filters.empty()) { return true; } for (const auto& name_contains : name_filters) { if (mapped_file_name.find(name_contains) != std::string::npos) { return true; } } return false; } #endif static void Usage(const char* cmd) { std::cout << "Usage: " << cmd << " [options] pid" << std::endl << " --contains=: Display sections containing string." << std::endl << " --help: Shows this message." << std::endl << " --verbose: Makes displays verbose." << std::endl; PrintLetterKey(); } NO_RETURN static void Abort(const char* msg) { std::cerr << msg; exit(1); } static int DexDiagMain(int argc, char* argv[]) { if (argc < 2) { Usage(argv[0]); return EXIT_FAILURE; } std::vector name_filters; // TODO: add option to track usage by class name, etc. for (int i = 1; i < argc - 1; ++i) { const std::string_view option(argv[i]); if (option == "--help") { Usage(argv[0]); return EXIT_SUCCESS; } else if (option == "--verbose") { g_verbose = true; } else if (StartsWith(option, "--contains=")) { std::string contains(option.substr(strlen("--contains="))); name_filters.push_back(contains); } else { Usage(argv[0]); return EXIT_FAILURE; } } // Art specific set up. InitLogging(argv, Abort); MemMap::Init(); #ifdef ART_TARGET_ANDROID pid_t pid; char* endptr; pid = (pid_t)strtol(argv[argc - 1], &endptr, 10); if (*endptr != '\0' || kill(pid, 0) != 0) { std::cerr << StringPrintf("Invalid PID \"%s\".\n", argv[argc - 1]) << std::endl; return EXIT_FAILURE; } // get libmeminfo process information. ProcMemInfo proc(pid); // Get the set of mappings by the specified process. const std::vector& maps = proc.Maps(); if (maps.empty()) { std::cerr << "Error listing maps." << std::endl; return EXIT_FAILURE; } bool match_found = false; // Process the mappings that are due to vdex or oat files. Printer printer; for (auto& vma : maps) { std::string mapped_file_name = vma.name; // Filter by name contains options (if any). if (!FilterByNameContains(mapped_file_name, name_filters)) { continue; } if (IsVdexFileMapping(mapped_file_name)) { if (!DisplayMappingIfFromVdexFile(proc, vma, &printer)) { return EXIT_FAILURE; } match_found = true; } else if (IsOatFileMapping(mapped_file_name)) { if (!DisplayMappingIfFromOatFile(proc, vma, &printer)) { return EXIT_FAILURE; } match_found = true; } } if (!match_found) { std::cerr << "No relevant memory maps were found." << std::endl; return EXIT_FAILURE; } #endif return EXIT_SUCCESS; } } // namespace art int main(int argc, char* argv[]) { return art::DexDiagMain(argc, argv); }