David Sehr | beca4fe | 2017-03-30 17:50:24 -0700 | [diff] [blame] | 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 <stdint.h> |
| 19 | #include <stdlib.h> |
| 20 | #include <string.h> |
| 21 | |
| 22 | #include <iostream> |
| 23 | #include <memory> |
| 24 | |
| 25 | #include "android-base/stringprintf.h" |
| 26 | |
| 27 | #include "dex_file.h" |
| 28 | #include "dex_ir.h" |
| 29 | #include "dex_ir_builder.h" |
| 30 | #include "pagemap/pagemap.h" |
| 31 | #include "runtime.h" |
| 32 | #include "vdex_file.h" |
| 33 | |
| 34 | namespace art { |
| 35 | |
| 36 | using android::base::StringPrintf; |
| 37 | |
Andreas Gampe | eeabc03 | 2017-04-06 05:37:08 +0000 | [diff] [blame] | 38 | static constexpr size_t kLineLength = 32; |
| 39 | |
David Sehr | beca4fe | 2017-03-30 17:50:24 -0700 | [diff] [blame] | 40 | static bool g_show_key = false; |
| 41 | static bool g_verbose = false; |
| 42 | static bool g_show_statistics = false; |
| 43 | |
| 44 | struct DexSectionInfo { |
| 45 | public: |
| 46 | std::string name; |
| 47 | char letter; |
| 48 | }; |
| 49 | |
| 50 | static const std::map<uint16_t, DexSectionInfo> kDexSectionInfoMap = { |
| 51 | { DexFile::kDexTypeHeaderItem, { "Header", 'H' } }, |
| 52 | { DexFile::kDexTypeStringIdItem, { "StringId", 'S' } }, |
| 53 | { DexFile::kDexTypeTypeIdItem, { "TypeId", 'T' } }, |
| 54 | { DexFile::kDexTypeProtoIdItem, { "ProtoId", 'P' } }, |
| 55 | { DexFile::kDexTypeFieldIdItem, { "FieldId", 'F' } }, |
| 56 | { DexFile::kDexTypeMethodIdItem, { "MethodId", 'M' } }, |
| 57 | { DexFile::kDexTypeClassDefItem, { "ClassDef", 'C' } }, |
| 58 | { DexFile::kDexTypeCallSiteIdItem, { "CallSiteId", 'z' } }, |
| 59 | { DexFile::kDexTypeMethodHandleItem, { "MethodHandle", 'Z' } }, |
| 60 | { DexFile::kDexTypeMapList, { "TypeMap", 'L' } }, |
| 61 | { DexFile::kDexTypeTypeList, { "TypeList", 't' } }, |
| 62 | { DexFile::kDexTypeAnnotationSetRefList, { "AnnotationSetReferenceItem", '1' } }, |
| 63 | { DexFile::kDexTypeAnnotationSetItem, { "AnnotationSetItem", '2' } }, |
| 64 | { DexFile::kDexTypeClassDataItem, { "ClassData", 'c' } }, |
| 65 | { DexFile::kDexTypeCodeItem, { "CodeItem", 'X' } }, |
| 66 | { DexFile::kDexTypeStringDataItem, { "StringData", 's' } }, |
| 67 | { DexFile::kDexTypeDebugInfoItem, { "DebugInfo", 'D' } }, |
| 68 | { DexFile::kDexTypeAnnotationItem, { "AnnotationItem", '3' } }, |
| 69 | { DexFile::kDexTypeEncodedArrayItem, { "EncodedArrayItem", 'E' } }, |
| 70 | { DexFile::kDexTypeAnnotationsDirectoryItem, { "AnnotationsDirectoryItem", '4' } } |
| 71 | }; |
| 72 | |
| 73 | class PageCount { |
| 74 | public: |
| 75 | PageCount() { |
| 76 | for (auto it = kDexSectionInfoMap.begin(); it != kDexSectionInfoMap.end(); ++it) { |
| 77 | map_[it->first] = 0; |
| 78 | } |
| 79 | } |
| 80 | void Increment(uint16_t type) { |
| 81 | map_[type]++; |
| 82 | } |
| 83 | size_t Get(uint16_t type) const { |
| 84 | return map_.at(type); |
| 85 | } |
| 86 | private: |
| 87 | std::map<uint16_t, size_t> map_; |
| 88 | DISALLOW_COPY_AND_ASSIGN(PageCount); |
| 89 | }; |
| 90 | |
| 91 | static void PrintLetterKey() { |
| 92 | std::cout << "letter section_type" << std::endl; |
| 93 | for (const auto& p : kDexSectionInfoMap) { |
| 94 | const DexSectionInfo& section_info = p.second; |
| 95 | std::cout << section_info.letter << " " << section_info.name.c_str() << std::endl; |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | static char PageTypeChar(uint16_t type) { |
| 100 | if (kDexSectionInfoMap.find(type) == kDexSectionInfoMap.end()) { |
| 101 | return '-'; |
| 102 | } |
| 103 | return kDexSectionInfoMap.find(type)->second.letter; |
| 104 | } |
| 105 | |
| 106 | static uint16_t FindSectionTypeForPage(size_t page, |
| 107 | const std::vector<dex_ir::DexFileSection>& sections) { |
| 108 | for (const auto& section : sections) { |
| 109 | size_t first_page_of_section = section.offset / kPageSize; |
| 110 | // Only consider non-empty sections. |
| 111 | if (section.size == 0) { |
| 112 | continue; |
| 113 | } |
| 114 | // Attribute the page to the highest-offset section that starts before the page. |
| 115 | if (first_page_of_section <= page) { |
| 116 | return section.type; |
| 117 | } |
| 118 | } |
| 119 | // If there's no non-zero sized section with an offset below offset we're looking for, it |
| 120 | // must be the header. |
| 121 | return DexFile::kDexTypeHeaderItem; |
| 122 | } |
| 123 | |
| 124 | static void ProcessPageMap(uint64_t* pagemap, |
| 125 | size_t start, |
| 126 | size_t end, |
| 127 | const std::vector<dex_ir::DexFileSection>& sections, |
| 128 | PageCount* page_counts) { |
| 129 | for (size_t page = start; page < end; ++page) { |
| 130 | char type_char = '.'; |
| 131 | if (PM_PAGEMAP_PRESENT(pagemap[page])) { |
| 132 | uint16_t type = FindSectionTypeForPage(page, sections); |
| 133 | page_counts->Increment(type); |
| 134 | type_char = PageTypeChar(type); |
| 135 | } |
| 136 | if (g_verbose) { |
| 137 | std::cout << type_char; |
| 138 | if ((page - start) % kLineLength == kLineLength - 1) { |
| 139 | std::cout << std::endl; |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | if (g_verbose) { |
| 144 | if ((end - start) % kLineLength != 0) { |
| 145 | std::cout << std::endl; |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | static void DisplayDexStatistics(size_t start, |
| 151 | size_t end, |
| 152 | const PageCount& resident_pages, |
| 153 | const std::vector<dex_ir::DexFileSection>& sections) { |
| 154 | // Compute the total possible sizes for sections. |
| 155 | PageCount mapped_pages; |
| 156 | DCHECK_GE(end, start); |
| 157 | size_t total_mapped_pages = end - start; |
| 158 | if (total_mapped_pages == 0) { |
| 159 | return; |
| 160 | } |
| 161 | for (size_t page = start; page < end; ++page) { |
| 162 | mapped_pages.Increment(FindSectionTypeForPage(page, sections)); |
| 163 | } |
| 164 | size_t total_resident_pages = 0; |
| 165 | // Compute the width of the section header column in the table (for fixed formatting). |
| 166 | int section_header_width = 0; |
| 167 | for (const auto& section_info : kDexSectionInfoMap) { |
| 168 | section_header_width = std::max(section_header_width, |
| 169 | static_cast<int>(section_info.second.name.length())); |
| 170 | } |
| 171 | // The width needed to print a file page offset (32-bit). |
| 172 | static constexpr int kPageCountWidth = |
| 173 | static_cast<int>(std::numeric_limits<uint32_t>::digits10); |
| 174 | // Display the sections. |
| 175 | static constexpr char kSectionHeader[] = "Section name"; |
| 176 | std::cout << StringPrintf("%-*s %*s %*s %% of %% of", |
| 177 | section_header_width, |
| 178 | kSectionHeader, |
| 179 | kPageCountWidth, |
| 180 | "resident", |
| 181 | kPageCountWidth, |
| 182 | "total" |
| 183 | ) |
| 184 | << std::endl; |
| 185 | std::cout << StringPrintf("%-*s %*s %*s sect. total", |
| 186 | section_header_width, |
| 187 | "", |
| 188 | kPageCountWidth, |
| 189 | "pages", |
| 190 | kPageCountWidth, |
| 191 | "pages") |
| 192 | << std::endl; |
| 193 | for (size_t i = sections.size(); i > 0; --i) { |
| 194 | const dex_ir::DexFileSection& section = sections[i - 1]; |
| 195 | const uint16_t type = section.type; |
| 196 | const DexSectionInfo& section_info = kDexSectionInfoMap.find(type)->second; |
| 197 | size_t pages_resident = resident_pages.Get(type); |
| 198 | double percent_resident = 0; |
| 199 | if (mapped_pages.Get(type) > 0) { |
| 200 | percent_resident = 100.0 * pages_resident / mapped_pages.Get(type); |
| 201 | } |
| 202 | // 6.2 is sufficient to print 0-100% with two decimal places of accuracy. |
| 203 | std::cout << StringPrintf("%-*s %*zd %*zd %6.2f %6.2f", |
| 204 | section_header_width, |
| 205 | section_info.name.c_str(), |
| 206 | kPageCountWidth, |
| 207 | pages_resident, |
| 208 | kPageCountWidth, |
| 209 | mapped_pages.Get(type), |
| 210 | percent_resident, |
| 211 | 100.0 * pages_resident / total_mapped_pages) |
| 212 | << std::endl; |
| 213 | total_resident_pages += pages_resident; |
| 214 | } |
| 215 | std::cout << StringPrintf("%-*s %*zd %*zd %6.2f", |
| 216 | section_header_width, |
| 217 | "GRAND TOTAL", |
| 218 | kPageCountWidth, |
| 219 | total_resident_pages, |
| 220 | kPageCountWidth, |
| 221 | total_mapped_pages, |
| 222 | 100.0 * total_resident_pages / total_mapped_pages) |
| 223 | << std::endl |
| 224 | << std::endl; |
| 225 | } |
| 226 | |
| 227 | static void ProcessOneDexMapping(uint64_t* pagemap, |
| 228 | uint64_t map_start, |
| 229 | const DexFile* dex_file, |
| 230 | uint64_t vdex_start) { |
| 231 | uint64_t dex_file_start = reinterpret_cast<uint64_t>(dex_file->Begin()); |
| 232 | size_t dex_file_size = dex_file->Size(); |
| 233 | if (dex_file_start < vdex_start) { |
| 234 | std::cerr << "Dex file start offset for " |
| 235 | << dex_file->GetLocation().c_str() |
| 236 | << " is incorrect: map start " |
| 237 | << StringPrintf("%zx > dex start %zx\n", map_start, dex_file_start) |
| 238 | << std::endl; |
| 239 | return; |
| 240 | } |
David Sehr | c0e638f | 2017-04-07 16:56:46 -0700 | [diff] [blame^] | 241 | uint64_t start_page = (dex_file_start - vdex_start) / kPageSize; |
| 242 | uint64_t start_address = start_page * kPageSize; |
| 243 | uint64_t end_page = RoundUp(start_address + dex_file_size, kPageSize) / kPageSize; |
David Sehr | beca4fe | 2017-03-30 17:50:24 -0700 | [diff] [blame] | 244 | std::cout << "DEX " |
| 245 | << dex_file->GetLocation().c_str() |
| 246 | << StringPrintf(": %zx-%zx", |
David Sehr | c0e638f | 2017-04-07 16:56:46 -0700 | [diff] [blame^] | 247 | map_start + start_page * kPageSize, |
| 248 | map_start + end_page * kPageSize) |
David Sehr | beca4fe | 2017-03-30 17:50:24 -0700 | [diff] [blame] | 249 | << std::endl; |
| 250 | // Build a list of the dex file section types, sorted from highest offset to lowest. |
| 251 | std::vector<dex_ir::DexFileSection> sections; |
| 252 | { |
| 253 | std::unique_ptr<dex_ir::Header> header(dex_ir::DexIrBuilder(*dex_file)); |
| 254 | sections = dex_ir::GetSortedDexFileSections(header.get(), |
| 255 | dex_ir::SortDirection::kSortDescending); |
| 256 | } |
| 257 | PageCount section_resident_pages; |
David Sehr | c0e638f | 2017-04-07 16:56:46 -0700 | [diff] [blame^] | 258 | ProcessPageMap(pagemap, start_page, end_page, sections, §ion_resident_pages); |
David Sehr | beca4fe | 2017-03-30 17:50:24 -0700 | [diff] [blame] | 259 | if (g_show_statistics) { |
David Sehr | c0e638f | 2017-04-07 16:56:46 -0700 | [diff] [blame^] | 260 | DisplayDexStatistics(start_page, end_page, section_resident_pages, sections); |
David Sehr | beca4fe | 2017-03-30 17:50:24 -0700 | [diff] [blame] | 261 | } |
| 262 | } |
| 263 | |
| 264 | static bool DisplayMappingIfFromVdexFile(pm_map_t* map) { |
| 265 | // Confirm that the map is from a vdex file. |
| 266 | static const char* suffixes[] = { ".vdex" }; |
| 267 | std::string vdex_name; |
| 268 | bool found = false; |
| 269 | for (size_t j = 0; j < sizeof(suffixes) / sizeof(suffixes[0]); ++j) { |
| 270 | if (strstr(pm_map_name(map), suffixes[j]) != nullptr) { |
| 271 | vdex_name = pm_map_name(map); |
| 272 | found = true; |
| 273 | break; |
| 274 | } |
| 275 | } |
| 276 | if (!found) { |
| 277 | return true; |
| 278 | } |
| 279 | // Extract all the dex files from the vdex file. |
| 280 | std::string error_msg; |
| 281 | std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_name, |
| 282 | false /*writeable*/, |
| 283 | false /*low_4gb*/, |
| 284 | &error_msg /*out*/)); |
| 285 | if (vdex == nullptr) { |
| 286 | std::cerr << "Could not open vdex file " |
| 287 | << vdex_name.c_str() |
| 288 | << ": error " |
| 289 | << error_msg.c_str() |
| 290 | << std::endl; |
| 291 | return false; |
| 292 | } |
| 293 | |
| 294 | std::vector<std::unique_ptr<const DexFile>> dex_files; |
| 295 | if (!vdex->OpenAllDexFiles(&dex_files, &error_msg)) { |
| 296 | std::cerr << "Dex files could not be opened for " |
| 297 | << vdex_name.c_str() |
| 298 | << ": error " |
| 299 | << error_msg.c_str() |
| 300 | << std::endl; |
| 301 | } |
| 302 | // Open the page mapping (one uint64_t per page) for the entire vdex mapping. |
| 303 | uint64_t* pagemap; |
| 304 | size_t len; |
| 305 | if (pm_map_pagemap(map, &pagemap, &len) != 0) { |
| 306 | std::cerr << "Error creating pagemap." << std::endl; |
| 307 | return false; |
| 308 | } |
| 309 | // Process the dex files. |
| 310 | std::cout << "MAPPING " |
| 311 | << pm_map_name(map) |
| 312 | << StringPrintf(": %zx-%zx", pm_map_start(map), pm_map_end(map)) |
| 313 | << std::endl; |
| 314 | for (const auto& dex_file : dex_files) { |
| 315 | ProcessOneDexMapping(pagemap, |
| 316 | pm_map_start(map), |
| 317 | dex_file.get(), |
| 318 | reinterpret_cast<uint64_t>(vdex->Begin())); |
| 319 | } |
| 320 | free(pagemap); |
| 321 | return true; |
| 322 | } |
| 323 | |
| 324 | |
| 325 | static void Usage(const char* cmd) { |
| 326 | std::cerr << "Usage: " << cmd << " [-k] [-s] [-v] pid" << std::endl |
| 327 | << " -k Shows a key to verbose display characters." << std::endl |
| 328 | << " -s Shows section statistics for individual dex files." << std::endl |
| 329 | << " -v Verbosely displays resident pages for dex files." << std::endl; |
| 330 | } |
| 331 | |
| 332 | static int DexDiagMain(int argc, char* argv[]) { |
| 333 | if (argc < 2) { |
| 334 | Usage(argv[0]); |
| 335 | return EXIT_FAILURE; |
| 336 | } |
| 337 | |
| 338 | // TODO: add option to track usage by class name, etc. |
| 339 | for (int i = 1; i < argc - 1; ++i) { |
| 340 | if (strcmp(argv[i], "-k") == 0) { |
| 341 | g_show_key = true; |
| 342 | } else if (strcmp(argv[i], "-s") == 0) { |
| 343 | g_show_statistics = true; |
| 344 | } else if (strcmp(argv[i], "-v") == 0) { |
| 345 | g_verbose = true; |
| 346 | } else { |
| 347 | Usage(argv[0]); |
| 348 | return EXIT_FAILURE; |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | // Art specific set up. |
| 353 | InitLogging(argv, Runtime::Aborter); |
| 354 | MemMap::Init(); |
| 355 | |
| 356 | pid_t pid; |
| 357 | char* endptr; |
| 358 | pid = (pid_t)strtol(argv[argc - 1], &endptr, 10); |
| 359 | if (*endptr != '\0' || kill(pid, 0) != 0) { |
| 360 | std::cerr << StringPrintf("Invalid PID \"%s\".\n", argv[argc - 1]) << std::endl; |
| 361 | return EXIT_FAILURE; |
| 362 | } |
| 363 | |
| 364 | // get libpagemap kernel information. |
| 365 | pm_kernel_t* ker; |
| 366 | if (pm_kernel_create(&ker) != 0) { |
| 367 | std::cerr << "Error creating kernel interface -- does this kernel have pagemap?" << std::endl; |
| 368 | return EXIT_FAILURE; |
| 369 | } |
| 370 | |
| 371 | // get libpagemap process information. |
| 372 | pm_process_t* proc; |
| 373 | if (pm_process_create(ker, pid, &proc) != 0) { |
| 374 | std::cerr << "Error creating process interface -- does process " |
| 375 | << pid |
| 376 | << " really exist?" |
| 377 | << std::endl; |
| 378 | return EXIT_FAILURE; |
| 379 | } |
| 380 | |
| 381 | // Get the set of mappings by the specified process. |
| 382 | pm_map_t** maps; |
| 383 | size_t num_maps; |
| 384 | if (pm_process_maps(proc, &maps, &num_maps) != 0) { |
| 385 | std::cerr << "Error listing maps." << std::endl; |
| 386 | return EXIT_FAILURE; |
| 387 | } |
| 388 | |
| 389 | // Process the mappings that are due to DEX files. |
| 390 | for (size_t i = 0; i < num_maps; ++i) { |
| 391 | if (!DisplayMappingIfFromVdexFile(maps[i])) { |
| 392 | return EXIT_FAILURE; |
| 393 | } |
| 394 | } |
David Sehr | beca4fe | 2017-03-30 17:50:24 -0700 | [diff] [blame] | 395 | |
| 396 | if (g_show_key) { |
| 397 | PrintLetterKey(); |
| 398 | } |
| 399 | return 0; |
| 400 | } |
| 401 | |
| 402 | } // namespace art |
| 403 | |
| 404 | int main(int argc, char* argv[]) { |
| 405 | return art::DexDiagMain(argc, argv); |
| 406 | } |