You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
541 lines
18 KiB
541 lines
18 KiB
/*
|
|
* 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 <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#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 <meminfo/pageacct.h>
|
|
#include <meminfo/procmeminfo.h>
|
|
#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<int>(std::numeric_limits<uint32_t>::digits10);
|
|
// Display the sections.
|
|
static constexpr char kSectionHeader[] = "Section name";
|
|
|
|
struct DexSectionInfo {
|
|
public:
|
|
std::string name;
|
|
char letter;
|
|
};
|
|
|
|
static const std::map<uint16_t, DexSectionInfo> 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<uint16_t, size_t> 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<int>(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<dex_ir::DexFileSection>& 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<uint64_t>& pagemap,
|
|
size_t start,
|
|
size_t end,
|
|
const std::vector<dex_ir::DexFileSection>& 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<dex_ir::DexFileSection>& 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<uint64_t>& pagemap,
|
|
uint64_t map_start,
|
|
const DexFile* dex_file,
|
|
uint64_t vdex_start,
|
|
Printer* printer) {
|
|
uint64_t dex_file_start = reinterpret_cast<uint64_t>(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<dex_ir::DexFileSection> sections;
|
|
{
|
|
Options options;
|
|
std::unique_ptr<dex_ir::Header> 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<VdexFile> 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<std::unique_ptr<const DexFile>> 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<uint64_t> 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<uint64_t>(vdex->Begin()),
|
|
printer);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void ProcessOneOatMapping(const std::vector<uint64_t>& 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<uint64_t> 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<std::string>& 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=<string>: 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<std::string> 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.
|
|
// Do not get the map usage stats, they are never used and it can take
|
|
// a long time to get this data.
|
|
const std::vector<Vma>& maps = proc.MapsWithoutUsageStats();
|
|
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);
|
|
}
|