diff options
author | Yabin Cui <yabinc@google.com> | 2018-03-26 17:34:00 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2018-03-28 17:16:27 -0700 |
commit | 516a87cd05e6f7dcf2c45fd8ba6d1d0e1e1e7bdd (patch) | |
tree | 642ab8433692d1862900ac93c7a24fc131ef4822 | |
parent | 81ed0ea0e64d536287e823431f4999f183cc3cf1 (diff) | |
download | extras-516a87cd05e6f7dcf2c45fd8ba6d1d0e1e1e7bdd.tar.gz |
simpleperf: support showing symbols for interpreted java code.
To convert from a dex_pc (returned by libunwindstack) to a symbol name,
we need below things:
1. The mapping info of the vdex file containing the dex_pc.
2. The offsets of dex files in the vdex file.
So make below changes:
1. Record none executable maps when profiling java code.
2. Refactor dso code to add a new type for dex file, using DexFileDso
to store dex file offsets in a vdex file, and load symbols from that
vdex file.
3. Add read_dex_file.cpp to read java symbols using libdexfile.
4. Change the format of file section in record_file_format.h, to store
dex file offsets in vdex files.
Bug: http://b/73126888
Bug: http://b/77236599
Test: Run simpleperf to profile several apps manually, can see
Test: callstacks of both java code and native code.
Test: Run simpleperf_unit_test.
Change-Id: I08005a03beb3df1a70db034bc463f555934856ba
-rw-r--r-- | simpleperf/Android.mk | 10 | ||||
-rw-r--r-- | simpleperf/JITDebugReader.cpp | 10 | ||||
-rw-r--r-- | simpleperf/JITDebugReader.h | 4 | ||||
-rw-r--r-- | simpleperf/cmd_debug_unwind.cpp | 1 | ||||
-rw-r--r-- | simpleperf/cmd_dumprecord.cpp | 9 | ||||
-rw-r--r-- | simpleperf/cmd_record.cpp | 63 | ||||
-rw-r--r-- | simpleperf/cmd_record_test.cpp | 4 | ||||
-rw-r--r-- | simpleperf/dso.cpp | 480 | ||||
-rw-r--r-- | simpleperf/dso.h | 45 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 2 | ||||
-rw-r--r-- | simpleperf/environment.h | 2 | ||||
-rw-r--r-- | simpleperf/event_selection_set.cpp | 12 | ||||
-rw-r--r-- | simpleperf/event_selection_set.h | 2 | ||||
-rw-r--r-- | simpleperf/nonlinux_support/nonlinux_support.cpp | 6 | ||||
-rw-r--r-- | simpleperf/read_dex_file.cpp | 108 | ||||
-rw-r--r-- | simpleperf/read_dex_file.h | 35 | ||||
-rw-r--r-- | simpleperf/read_dex_file_test.cpp | 40 | ||||
-rw-r--r-- | simpleperf/record_file.h | 11 | ||||
-rw-r--r-- | simpleperf/record_file_format.h | 7 | ||||
-rw-r--r-- | simpleperf/record_file_reader.cpp | 15 | ||||
-rw-r--r-- | simpleperf/record_file_writer.cpp | 20 | ||||
-rw-r--r-- | simpleperf/thread_tree.cpp | 28 | ||||
-rw-r--r-- | simpleperf/thread_tree.h | 7 | ||||
-rw-r--r-- | simpleperf/tracing.cpp | 2 |
24 files changed, 602 insertions, 321 deletions
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk index 7a412516..d6ea1243 100644 --- a/simpleperf/Android.mk +++ b/simpleperf/Android.mk @@ -120,6 +120,7 @@ libsimpleperf_src_files_linux := \ JITDebugReader.cpp \ OfflineUnwinder.cpp \ perf_clock.cpp \ + read_dex_file.cpp \ record_file_writer.cpp \ UnixSocket.cpp \ workload.cpp \ @@ -144,6 +145,8 @@ LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_target) LOCAL_MULTILIB := both LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static include $(LLVM_DEVICE_BUILD_MK) +# Remove -std=c++11 flag to compile read_dex_file.cpp. +LOCAL_CPPFLAGS := $(filter-out $(LOCAL_CPPFLAGS),-std=c++11) include $(BUILD_STATIC_LIBRARY) # libsimpleperf host @@ -165,6 +168,8 @@ LOCAL_MULTILIB := both LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static LOCAL_CXX_STL := libc++_static include $(LLVM_HOST_BUILD_MK) +# Remove -std=c++11 flag to compile read_dex_file.cpp. +LOCAL_CPPFLAGS := $(filter-out $(LOCAL_CPPFLAGS),-std=c++11) include $(BUILD_HOST_STATIC_LIBRARY) @@ -372,6 +377,7 @@ simpleperf_unit_test_src_files_linux := \ cmd_trace_sched_test.cpp \ environment_test.cpp \ IOEventLoop_test.cpp \ + read_dex_file_test.cpp \ record_file_test.cpp \ UnixSocket_test.cpp \ workload_test.cpp \ @@ -459,6 +465,8 @@ LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_target) LOCAL_MULTILIB := both LOCAL_FORCE_STATIC_EXECUTABLE := true include $(LLVM_DEVICE_BUILD_MK) +# Remove -std=c++11 flag to compile read_dex_file.cpp. +LOCAL_CPPFLAGS := $(filter-out $(LOCAL_CPPFLAGS),-std=c++11) include $(BUILD_STATIC_TEST_LIBRARY) # libsimpleperf_cts_test linux host @@ -473,6 +481,8 @@ LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux) LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux) LOCAL_MULTILIB := both include $(LLVM_HOST_BUILD_MK) +# Remove -std=c++11 flag to compile read_dex_file.cpp. +LOCAL_CPPFLAGS := $(filter-out $(LOCAL_CPPFLAGS),-std=c++11) include $(BUILD_HOST_STATIC_TEST_LIBRARY) # simpleperf_record_test diff --git a/simpleperf/JITDebugReader.cpp b/simpleperf/JITDebugReader.cpp index ae680e73..cb27996a 100644 --- a/simpleperf/JITDebugReader.cpp +++ b/simpleperf/JITDebugReader.cpp @@ -431,14 +431,12 @@ void JITDebugReader::ReadDexSymFiles(const std::vector<CodeEntry>& dex_entries, // Offset of dex file in .vdex file or .apk file. uint64_t dex_file_offset = dex_entry.symfile_addr - it->start_addr + it->pgoff; DexSymFile symfile; - symfile.addr = dex_entry.symfile_addr; - symfile.len = dex_entry.symfile_size; - symfile.pgoff = dex_file_offset; + symfile.dex_file_offset = dex_file_offset; symfile.file_path = it->name; dex_symfiles->push_back(symfile); - LOG(VERBOSE) << "DexFile " << symfile.file_path << "+" << std::hex << dex_file_offset - << " at [" << std::hex << symfile.addr << " - " << (symfile.addr + symfile.len) - << "] with size " << symfile.len; + LOG(VERBOSE) << "DexFile " << symfile.file_path << "+" << std::hex << symfile.dex_file_offset + << " in map [" << it->start_addr << " - " << (it->start_addr + it->len) + << "] with size " << dex_entry.symfile_size; } } diff --git a/simpleperf/JITDebugReader.h b/simpleperf/JITDebugReader.h index 22149190..191b351c 100644 --- a/simpleperf/JITDebugReader.h +++ b/simpleperf/JITDebugReader.h @@ -37,9 +37,7 @@ struct JITSymFile { }; struct DexSymFile { - uint64_t addr; // The start addr of the dex file in memory. - uint64_t len; // The size of the dex file in memory. - uint64_t pgoff; // The file offset of the dex file in the file containing it + uint64_t dex_file_offset; // The offset of the dex file in the file containing it std::string file_path; // The path of file containing the dex file }; diff --git a/simpleperf/cmd_debug_unwind.cpp b/simpleperf/cmd_debug_unwind.cpp index ed962f9e..d0276409 100644 --- a/simpleperf/cmd_debug_unwind.cpp +++ b/simpleperf/cmd_debug_unwind.cpp @@ -190,6 +190,7 @@ bool DebugUnwindCommand::UnwindRecordFile() { if (!reader_) { return false; } + reader_->LoadBuildIdAndFileFeatures(thread_tree_); std::string record_cmd = android::base::Join(reader_->ReadCmdlineFeature(), " "); if (record_cmd.find("--no-unwind") == std::string::npos || (record_cmd.find("-g") == std::string::npos && diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp index 97bd7132..8fb3d672 100644 --- a/simpleperf/cmd_dumprecord.cpp +++ b/simpleperf/cmd_dumprecord.cpp @@ -235,11 +235,12 @@ bool DumpRecordCommand::DumpFeatureSection() { uint32_t file_type; uint64_t min_vaddr; std::vector<Symbol> symbols; + std::vector<uint64_t> dex_file_offsets; size_t read_pos = 0; PrintIndented(1, "file:\n"); while (record_file_reader_->ReadFileFeature(read_pos, &file_path, &file_type, &min_vaddr, - &symbols)) { + &symbols, &dex_file_offsets)) { PrintIndented(2, "file_path %s\n", file_path.c_str()); PrintIndented(2, "file_type %s\n", DsoTypeToString(static_cast<DsoType>(file_type))); PrintIndented(2, "min_vaddr 0x%" PRIx64 "\n", min_vaddr); @@ -248,6 +249,12 @@ bool DumpRecordCommand::DumpFeatureSection() { PrintIndented(3, "%s [0x%" PRIx64 "-0x%" PRIx64 "]\n", symbol.DemangledName(), symbol.addr, symbol.addr + symbol.len); } + if (file_type == static_cast<uint32_t>(DSO_DEX_FILE)) { + PrintIndented(2, "dex_file_offsets:\n"); + for (uint64_t offset : dex_file_offsets) { + PrintIndented(3, "0x%" PRIx64 "\n", offset); + } + } } } else if (feature == FEAT_META_INFO) { std::unordered_map<std::string, std::string> info_map; diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index 6b7ce94d..5ea42573 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -252,6 +252,7 @@ class RecordCommand : public Command { bool DumpKernelAndModuleMmaps(const perf_event_attr& attr, uint64_t event_id); bool DumpThreadCommAndMmaps(const perf_event_attr& attr, uint64_t event_id); bool ProcessRecord(Record* record); + bool ShouldOmitRecord(Record* record); bool SaveRecordForPostUnwinding(Record* record); bool SaveRecordAfterUnwinding(Record* record); bool SaveRecordWithoutUnwinding(Record* record); @@ -415,6 +416,16 @@ bool RecordCommand::PrepareRecording(Workload* workload) { } else { need_to_check_targets = true; } + // Profiling JITed/interpreted Java code is supported starting from Android P. + if (app_pid != 0 && GetAndroidVersion() >= kAndroidVersionP) { + // JIT symfiles are stored in temporary files, and are deleted after recording. But if + // `-g --no-unwind` option is used, we want to keep symfiles to support unwinding in + // the debug-unwind cmd. + bool keep_symfiles = dwarf_callchain_sampling_ && !unwind_dwarf_callchain_; + jit_debug_reader_.reset(new JITDebugReader(app_pid, keep_symfiles)); + // To profile java code, need to dump maps containing vdex files, which are not executable. + event_selection_set_.SetRecordNotExecutableMaps(true); + } // 5. Open perf event files and create mapped buffers. if (!event_selection_set_.OpenEventFiles(cpus_)) { @@ -458,14 +469,7 @@ bool RecordCommand::PrepareRecording(Workload* workload) { return false; } } - // Profiling JITed/interpreted code is supported starting from Android P. - const int kAndroidVersionP = 9; - if (app_pid != 0 && GetAndroidVersion() >= kAndroidVersionP) { - // JIT symfiles are stored in temporary files, and are deleted after recording. But if - // `-g --no-unwind` option is used, we want to keep symfiles to support unwinding in - // the debug-unwind cmd. - bool keep_symfiles = dwarf_callchain_sampling_ && !unwind_dwarf_callchain_; - jit_debug_reader_.reset(new JITDebugReader(app_pid, keep_symfiles)); + if (jit_debug_reader_) { // Update JIT info at the beginning of recording. if (!UpdateJITDebugInfo()) { return false; @@ -1006,8 +1010,8 @@ bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr& attr, continue; } for (const auto& map : thread_mmaps) { - if (map.executable == 0) { - continue; // No need to dump non-executable mmap info. + if (map.executable == 0 && !event_selection_set_.RecordNotExecutableMaps()) { + continue; } MmapRecord record(attr, false, pid, pid, map.start_addr, map.len, map.pgoff, map.name, event_id); @@ -1047,6 +1051,9 @@ bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr& attr, } bool RecordCommand::ProcessRecord(Record* record) { + if (ShouldOmitRecord(record)) { + return true; + } last_record_timestamp_ = record->Timestamp(); if (unwind_dwarf_callchain_) { if (post_unwind_) { @@ -1057,7 +1064,32 @@ bool RecordCommand::ProcessRecord(Record* record) { return SaveRecordWithoutUnwinding(record); } +template <typename MmapRecordType> +bool IsMappingOnlyExistInMemory(MmapRecordType* record) { + return !record->InKernel() && !IsRegularFile(record->filename) && record->filename != "[vdso]"; +} + +bool RecordCommand::ShouldOmitRecord(Record* record) { + if (jit_debug_reader_) { + // To profile jitted Java code, we need PROT_JIT_SYMFILE_MAP maps not overlapped by maps for + // /dev/ashmem/dalvik-jit-code-cache. To profile interpreted Java code, we record maps that + // are not executable. Some non-exec maps (like those for stack, heap) provide misleading map + // entries for unwinding, as in http://b/77236599. So it is better to remove + // dalvik-jit-code-cache and other maps that only exist in memory. + switch (record->type()) { + case PERF_RECORD_MMAP: + return IsMappingOnlyExistInMemory(static_cast<MmapRecord*>(record)); + case PERF_RECORD_MMAP2: + return IsMappingOnlyExistInMemory(static_cast<Mmap2Record*>(record)); + } + } + return false; +} + bool RecordCommand::SaveRecordForPostUnwinding(Record* record) { + if (ShouldOmitRecord(record)) { + return true; + } if (record->type() == PERF_RECORD_SAMPLE) { static_cast<SampleRecord*>(record)->RemoveInvalidStackData(); } @@ -1131,7 +1163,9 @@ bool RecordCommand::UpdateJITDebugInfo() { return false; } } - // TODO: Handle dex symfiles. + for (auto& symfile : dex_symfiles) { + thread_tree_.AddDexFileOffset(symfile.file_path, symfile.dex_file_offset); + } return true; } @@ -1285,20 +1319,17 @@ bool RecordCommand::DumpAdditionalFeatures( return false; } - size_t feature_count = 5; + size_t feature_count = 6; if (branch_sampling_) { feature_count++; } - if (dump_symbols_) { - feature_count++; - } if (!record_file_writer_->BeginWriteFeatures(feature_count)) { return false; } if (!DumpBuildIdFeature()) { return false; } - if (dump_symbols_ && !DumpFileFeature()) { + if (!DumpFileFeature()) { return false; } utsname uname_buf; diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index 7f208862..e6c11aeb 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -389,9 +389,11 @@ TEST(record_cmd, dump_kernel_symbols) { uint32_t file_type; uint64_t min_vaddr; std::vector<Symbol> symbols; + std::vector<uint64_t> dex_file_offsets; size_t read_pos = 0; bool has_kernel_symbols = false; - while (reader->ReadFileFeature(read_pos, &file_path, &file_type, &min_vaddr, &symbols)) { + while (reader->ReadFileFeature(read_pos, &file_path, &file_type, &min_vaddr, &symbols, + &dex_file_offsets)) { if (file_type == DSO_KERNEL && !symbols.empty()) { has_kernel_symbols = true; } diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp index 4825ac72..f3e9b2ce 100644 --- a/simpleperf/dso.cpp +++ b/simpleperf/dso.cpp @@ -28,6 +28,7 @@ #include "environment.h" #include "read_apk.h" +#include "read_dex_file.h" #include "read_elf.h" #include "utils.h" @@ -38,7 +39,8 @@ Symbol::Symbol(const std::string& name, uint64_t addr, uint64_t len) len(len), name_(symbol_name_allocator.AllocateString(name)), demangled_name_(nullptr), - dump_id_(UINT_MAX) {} + dump_id_(UINT_MAX) { +} const char* Symbol::DemangledName() const { if (demangled_name_ == nullptr) { @@ -141,41 +143,14 @@ BuildId Dso::GetExpectedBuildId() { return FindExpectedBuildIdForPath(path_); } -std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_path, - bool force_64bit) { - return std::unique_ptr<Dso>(new Dso(dso_type, dso_path, force_64bit)); -} - -Dso::Dso(DsoType type, const std::string& path, bool force_64bit) +Dso::Dso(DsoType type, const std::string& path, const std::string& debug_file_path) : type_(type), path_(path), - debug_file_path_(path), - min_vaddr_(std::numeric_limits<uint64_t>::max()), + debug_file_path_(debug_file_path), is_loaded_(false), dump_id_(UINT_MAX), symbol_dump_id_(0), symbol_warning_loglevel_(android::base::WARNING) { - if (type_ == DSO_KERNEL) { - min_vaddr_ = 0; - } - // Check if file matching path_ exists in symfs directory before using it as - // debug_file_path_. - if (!symfs_dir_.empty()) { - std::string path_in_symfs = symfs_dir_ + path_; - std::tuple<bool, std::string, std::string> tuple = - SplitUrlInApk(path_in_symfs); - std::string file_path = - std::get<0>(tuple) ? std::get<1>(tuple) : path_in_symfs; - if (IsRegularFile(file_path)) { - debug_file_path_ = path_in_symfs; - } - } else if (path == "[vdso]") { - if (force_64bit && !vdso_64bit_.empty()) { - debug_file_path_ = vdso_64bit_; - } else if (!force_64bit && !vdso_32bit_.empty()) { - debug_file_path_ = vdso_32bit_; - } - } size_t pos = path.find_last_of("/\\"); if (pos != std::string::npos) { file_name_ = path.substr(pos + 1); @@ -214,15 +189,13 @@ const Symbol* Dso::FindSymbol(uint64_t vaddr_in_dso) { if (!is_loaded_) { Load(); } - if (!symbols_.empty()) { - auto it = std::upper_bound(symbols_.begin(), symbols_.end(), - Symbol("", vaddr_in_dso, 0), - Symbol::CompareValueByAddr); - if (it != symbols_.begin()) { - --it; - if (it->addr <= vaddr_in_dso && (it->addr + it->len > vaddr_in_dso)) { - return &*it; - } + auto it = std::upper_bound(symbols_.begin(), symbols_.end(), + Symbol("", vaddr_in_dso, 0), + Symbol::CompareValueByAddr); + if (it != symbols_.begin()) { + --it; + if (it->addr <= vaddr_in_dso && (it->addr + it->len > vaddr_in_dso)) { + return &*it; } } if (!unknown_symbols_.empty()) { @@ -234,13 +207,6 @@ const Symbol* Dso::FindSymbol(uint64_t vaddr_in_dso) { return nullptr; } -const std::vector<Symbol>& Dso::GetSymbols() { - if (!is_loaded_) { - Load(); - } - return symbols_; -} - void Dso::SetSymbols(std::vector<Symbol>* symbols) { symbols_ = std::move(*symbols); symbols->clear(); @@ -250,252 +216,248 @@ void Dso::AddUnknownSymbol(uint64_t vaddr_in_dso, const std::string& name) { unknown_symbols_.insert(std::make_pair(vaddr_in_dso, Symbol(name, vaddr_in_dso, 1))); } -uint64_t Dso::MinVirtualAddress() { - if (min_vaddr_ == std::numeric_limits<uint64_t>::max()) { - min_vaddr_ = 0; - if (type_ == DSO_ELF_FILE) { - BuildId build_id = GetExpectedBuildId(); - - uint64_t addr; - ElfStatus result = ReadMinExecutableVirtualAddressFromElfFile( - GetDebugFilePath(), build_id, &addr); - if (result != ElfStatus::NO_ERROR) { - LOG(WARNING) << "failed to read min virtual address of " - << GetDebugFilePath() << ": " << result; - } else { - min_vaddr_ = addr; - } - } - } - return min_vaddr_; -} - -static std::vector<Symbol> MergeSortedSymbols(const std::vector<Symbol>& s1, - const std::vector<Symbol>& s2) { - std::vector<Symbol> result; - std::set_union(s1.begin(), s1.end(), s2.begin(), s2.end(), std::back_inserter(result), - Symbol::CompareValueByAddr); - return result; -} - void Dso::Load() { is_loaded_ = true; - std::vector<Symbol> dumped_symbols; - if (!symbols_.empty()) { - // If symbols has been read from file feature section of perf.data, move it to - // dumped_symbols, so later we can merge them with symbols read from file system. - dumped_symbols = std::move(symbols_); - symbols_.clear(); - // Don't warn missing symbol table if we have dumped symbols in perf.data. - symbol_warning_loglevel_ = android::base::DEBUG; - } - bool result = false; - switch (type_) { - case DSO_KERNEL: - result = LoadKernel(); - break; - case DSO_KERNEL_MODULE: - result = LoadKernelModule(); - break; - case DSO_ELF_FILE: { - if (std::get<0>(SplitUrlInApk(path_))) { - result = LoadEmbeddedElfFile(); - } else { - result = LoadElfFile(); - } - break; - } - } - if (result) { - std::sort(symbols_.begin(), symbols_.end(), Symbol::CompareValueByAddr); - FixupSymbolLength(); - } else { - symbols_.clear(); - } - - if (symbols_.empty()) { - symbols_ = std::move(dumped_symbols); - } else if (!dumped_symbols.empty()) { - symbols_ = MergeSortedSymbols(symbols_, dumped_symbols); - } - + std::vector<Symbol> symbols = LoadSymbols(); if (symbols_.empty()) { - LOG(DEBUG) << "failed to load dso: " << path_; - } -} - -static bool IsKernelFunctionSymbol(const KernelSymbol& symbol) { - return (symbol.type == 'T' || symbol.type == 't' || symbol.type == 'W' || - symbol.type == 'w'); -} - -static bool KernelSymbolCallback(const KernelSymbol& kernel_symbol, - std::vector<Symbol>* symbols) { - if (IsKernelFunctionSymbol(kernel_symbol)) { - symbols->emplace_back(Symbol(kernel_symbol.name, kernel_symbol.addr, 0)); - } - return false; -} - -static void VmlinuxSymbolCallback(const ElfFileSymbol& elf_symbol, - std::vector<Symbol>* symbols) { - if (elf_symbol.is_func) { - symbols->emplace_back( - Symbol(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len)); + symbols_ = std::move(symbols); + } else { + std::vector<Symbol> merged_symbols; + std::set_union(symbols_.begin(), symbols_.end(), symbols.begin(), symbols.end(), + std::back_inserter(merged_symbols), Symbol::CompareValueByAddr); + symbols_ = std::move(merged_symbols); } } -bool Dso::CheckReadSymbolResult(ElfStatus result, const std::string& filename) { +static void ReportReadElfSymbolResult(ElfStatus result, const std::string& path, + const std::string& debug_file_path, + android::base::LogSeverity warning_loglevel = android::base::WARNING) { if (result == ElfStatus::NO_ERROR) { - LOG(VERBOSE) << "Read symbols from " << filename << " successfully"; - return true; + LOG(VERBOSE) << "Read symbols from " << debug_file_path << " successfully"; } else if (result == ElfStatus::NO_SYMBOL_TABLE) { - if (path_ == "[vdso]") { + if (path == "[vdso]") { // Vdso only contains dynamic symbol table, and we can't change that. - return true; + return; } // Lacking symbol table isn't considered as an error but worth reporting. - LOG(symbol_warning_loglevel_) << filename << " doesn't contain symbol table"; - return true; + LOG(warning_loglevel) << debug_file_path << " doesn't contain symbol table"; } else { - LOG(symbol_warning_loglevel_) << "failed to read symbols from " << filename << ": " << result; - return false; + LOG(warning_loglevel) << "failed to read symbols from " << debug_file_path << ": " << result; + } +} + +static void SortAndFixSymbols(std::vector<Symbol>& symbols) { + std::sort(symbols.begin(), symbols.end(), Symbol::CompareValueByAddr); + Symbol* prev_symbol = nullptr; + for (auto& symbol : symbols) { + if (prev_symbol != nullptr && prev_symbol->len == 0) { + prev_symbol->len = symbol.addr - prev_symbol->addr; + } + prev_symbol = &symbol; } } -bool Dso::LoadKernel() { - BuildId build_id = GetExpectedBuildId(); - if (!vmlinux_.empty()) { - ElfStatus result = ParseSymbolsFromElfFile(vmlinux_, build_id, - std::bind(VmlinuxSymbolCallback, std::placeholders::_1, &symbols_)); - return CheckReadSymbolResult(result, vmlinux_); - } else if (!kallsyms_.empty()) { - ProcessKernelSymbols(kallsyms_, std::bind(&KernelSymbolCallback, - std::placeholders::_1, &symbols_)); - bool all_zero = true; - for (const auto& symbol : symbols_) { - if (symbol.addr != 0) { - all_zero = false; - break; +class ElfDso : public Dso { + public: + ElfDso(const std::string& path, const std::string& debug_file_path) + : Dso(DSO_ELF_FILE, path, debug_file_path), + min_vaddr_(std::numeric_limits<uint64_t>::max()) {} + + uint64_t MinVirtualAddress() override { + if (min_vaddr_ == std::numeric_limits<uint64_t>::max()) { + min_vaddr_ = 0; + if (type_ == DSO_ELF_FILE) { + BuildId build_id = GetExpectedBuildId(); + + uint64_t addr; + ElfStatus result = ReadMinExecutableVirtualAddressFromElfFile( + GetDebugFilePath(), build_id, &addr); + if (result != ElfStatus::NO_ERROR) { + LOG(WARNING) << "failed to read min virtual address of " + << GetDebugFilePath() << ": " << result; + } else { + min_vaddr_ = addr; + } } } - if (all_zero) { - LOG(symbol_warning_loglevel_) - << "Symbol addresses in /proc/kallsyms on device are all zero. " - "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible."; - symbols_.clear(); - return false; + return min_vaddr_; + } + + void SetMinVirtualAddress(uint64_t min_vaddr) override { + min_vaddr_ = min_vaddr; + } + + protected: + std::vector<Symbol> LoadSymbols() override { + std::vector<Symbol> symbols; + BuildId build_id = GetExpectedBuildId(); + auto symbol_callback = [&](const ElfFileSymbol& symbol) { + if (symbol.is_func || (symbol.is_label && symbol.is_in_text_section)) { + symbols.emplace_back(symbol.name, symbol.vaddr, symbol.len); + } + }; + ElfStatus status; + std::tuple<bool, std::string, std::string> tuple = SplitUrlInApk(debug_file_path_); + if (std::get<0>(tuple)) { + status = ParseSymbolsFromApkFile(std::get<1>(tuple), std::get<2>(tuple), build_id, + symbol_callback); + } else { + status = ParseSymbolsFromElfFile(debug_file_path_, build_id, symbol_callback); } - } else if (read_kernel_symbols_from_proc_ || !build_id.IsEmpty()) { - // Try /proc/kallsyms only when asked to do so, or when build id matches. - // Otherwise, it is likely to use /proc/kallsyms on host for perf.data recorded on device. - if (!build_id.IsEmpty()) { - BuildId real_build_id; - if (!GetKernelBuildId(&real_build_id)) { - return false; + ReportReadElfSymbolResult(status, path_, debug_file_path_, + symbols_.empty() ? android::base::WARNING : android::base::DEBUG); + SortAndFixSymbols(symbols); + return symbols; + } + + private: + uint64_t min_vaddr_; +}; + +class KernelDso : public Dso { + public: + KernelDso(const std::string& path, const std::string& debug_file_path) + : Dso(DSO_KERNEL, path, debug_file_path) {} + + protected: + std::vector<Symbol> LoadSymbols() override { + std::vector<Symbol> symbols; + BuildId build_id = GetExpectedBuildId(); + if (!vmlinux_.empty()) { + auto symbol_callback = [&](const ElfFileSymbol& symbol) { + if (symbol.is_func) { + symbols.emplace_back(symbol.name, symbol.vaddr, symbol.len); + } + }; + ElfStatus status = ParseSymbolsFromElfFile(vmlinux_, build_id, symbol_callback); + ReportReadElfSymbolResult(status, path_, vmlinux_); + } else if (!kallsyms_.empty()) { + symbols = ReadSymbolsFromKallsyms(kallsyms_); + } else if (read_kernel_symbols_from_proc_ || !build_id.IsEmpty()) { + // Try /proc/kallsyms only when asked to do so, or when build id matches. + // Otherwise, it is likely to use /proc/kallsyms on host for perf.data recorded on device. + bool can_read_kallsyms = true; + if (!build_id.IsEmpty()) { + BuildId real_build_id; + if (!GetKernelBuildId(&real_build_id) || build_id != real_build_id) { + LOG(DEBUG) << "failed to read symbols from /proc/kallsyms: Build id mismatch"; + can_read_kallsyms = false; + } } - bool match = (build_id == real_build_id); - if (!match) { - LOG(symbol_warning_loglevel_) << "failed to read symbols from /proc/kallsyms: Build id " - << "mismatch"; - return false; + if (can_read_kallsyms) { + std::string kallsyms; + if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) { + LOG(DEBUG) << "failed to read /proc/kallsyms"; + } else { + symbols = ReadSymbolsFromKallsyms(kallsyms); + } } } - - std::string kallsyms; - if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) { - LOG(DEBUG) << "failed to read /proc/kallsyms"; - return false; + SortAndFixSymbols(symbols); + if (!symbols.empty()) { + symbols.back().len = std::numeric_limits<uint64_t>::max() - symbols.back().addr; } - ProcessKernelSymbols(kallsyms, std::bind(&KernelSymbolCallback, - std::placeholders::_1, &symbols_)); - bool all_zero = true; - for (const auto& symbol : symbols_) { - if (symbol.addr != 0) { - all_zero = false; - break; + return symbols; + } + + private: + std::vector<Symbol> ReadSymbolsFromKallsyms(std::string& kallsyms) { + std::vector<Symbol> symbols; + auto symbol_callback = [&](const KernelSymbol& symbol) { + if (strchr("TtWw", symbol.type) && symbol.addr != 0u) { + symbols.emplace_back(symbol.name, symbol.addr, 0); } - } - if (all_zero) { - LOG(symbol_warning_loglevel_) << "Symbol addresses in /proc/kallsyms are all zero. " - "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible."; - symbols_.clear(); return false; + }; + ProcessKernelSymbols(kallsyms, symbol_callback); + if (symbols.empty()) { + LOG(WARNING) << "Symbol addresses in /proc/kallsyms on device are all zero. " + "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible."; } + return symbols; } - return true; -} - -static void ElfFileSymbolCallback(const ElfFileSymbol& elf_symbol, - bool (*filter)(const ElfFileSymbol&), - std::vector<Symbol>* symbols) { - if (filter(elf_symbol)) { - symbols->emplace_back(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len); +}; + +class KernelModuleDso : public Dso { + public: + KernelModuleDso(const std::string& path, const std::string& debug_file_path) + : Dso(DSO_KERNEL_MODULE, path, debug_file_path) {} + + protected: + std::vector<Symbol> LoadSymbols() override { + std::vector<Symbol> symbols; + BuildId build_id = GetExpectedBuildId(); + auto symbol_callback = [&](const ElfFileSymbol& symbol) { + if (symbol.is_func || symbol.is_in_text_section) { + symbols.emplace_back(symbol.name, symbol.vaddr, symbol.len); + } + }; + ElfStatus status = ParseSymbolsFromElfFile(debug_file_path_, build_id, symbol_callback); + ReportReadElfSymbolResult(status, path_, debug_file_path_, + symbols_.empty() ? android::base::WARNING : android::base::DEBUG); + SortAndFixSymbols(symbols); + return symbols; } -} - -static bool SymbolFilterForKernelModule(const ElfFileSymbol& elf_symbol) { - // TODO: Parse symbol outside of .text section. - return (elf_symbol.is_func && elf_symbol.is_in_text_section); -} - -bool Dso::LoadKernelModule() { - BuildId build_id = GetExpectedBuildId(); - ElfStatus result = ParseSymbolsFromElfFile(GetDebugFilePath(), build_id, - std::bind(ElfFileSymbolCallback, std::placeholders::_1, - SymbolFilterForKernelModule, &symbols_)); - return CheckReadSymbolResult(result, GetDebugFilePath()); -} +}; -static bool SymbolFilterForDso(const ElfFileSymbol& elf_symbol) { - return elf_symbol.is_func || - (elf_symbol.is_label && elf_symbol.is_in_text_section); -} - -bool Dso::LoadElfFile() { - BuildId build_id = GetExpectedBuildId(); - - if (symfs_dir_.empty()) { - // Linux host can store debug shared libraries in /usr/lib/debug. - ElfStatus result = ParseSymbolsFromElfFile( - "/usr/lib/debug" + path_, build_id, - std::bind(ElfFileSymbolCallback, std::placeholders::_1, - SymbolFilterForDso, &symbols_)); - if (result == ElfStatus::NO_ERROR) { - return CheckReadSymbolResult(result, "/usr/lib/debug" + path_); +std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_path, + bool force_64bit) { + auto find_debug_file = [&]() { + // Check if file matching path_ exists in symfs directory before using it as + // debug_file_path_. + if (!symfs_dir_.empty()) { + std::string path_in_symfs = symfs_dir_ + dso_path; + std::tuple<bool, std::string, std::string> tuple = SplitUrlInApk(path_in_symfs); + std::string file_path = std::get<0>(tuple) ? std::get<1>(tuple) : path_in_symfs; + if (IsRegularFile(file_path)) { + return path_in_symfs; + } + } else if (dso_path == "[vdso]") { + if (force_64bit && !vdso_64bit_.empty()) { + return vdso_64bit_; + } else if (!force_64bit && !vdso_32bit_.empty()) { + return vdso_32bit_; + } + } else if (dso_type == DSO_ELF_FILE) { + // Linux host can store debug shared libraries in /usr/lib/debug. + std::string path = "/usr/lib/debug" + dso_path; + if (IsRegularFile(path)) { + return path; + } } - } - // TODO: load std::vector<Symbol> directly from ParseSymbolsFromElfFile - // instead of needing to call a callback function for each symbol. - ElfStatus result = ParseSymbolsFromElfFile( - GetDebugFilePath(), build_id, - std::bind(ElfFileSymbolCallback, std::placeholders::_1, - SymbolFilterForDso, &symbols_)); - return CheckReadSymbolResult(result, GetDebugFilePath()); -} + return dso_path; + }; -bool Dso::LoadEmbeddedElfFile() { - BuildId build_id = GetExpectedBuildId(); - auto tuple = SplitUrlInApk(GetDebugFilePath()); - CHECK(std::get<0>(tuple)); - ElfStatus result = ParseSymbolsFromApkFile( - std::get<1>(tuple), std::get<2>(tuple), build_id, - std::bind(ElfFileSymbolCallback, std::placeholders::_1, - SymbolFilterForDso, &symbols_)); - return CheckReadSymbolResult(result, GetDebugFilePath()); + switch (dso_type) { + case DSO_ELF_FILE: + return std::unique_ptr<Dso>(new ElfDso(dso_path, find_debug_file())); + case DSO_KERNEL: + return std::unique_ptr<Dso>(new KernelDso(dso_path, dso_path)); + case DSO_KERNEL_MODULE: + return std::unique_ptr<Dso>(new KernelModuleDso(dso_path, find_debug_file())); + case DSO_DEX_FILE: + return std::unique_ptr<Dso>(new DexFileDso(dso_path, find_debug_file())); + default: + LOG(FATAL) << "Unexpected dso_type " << static_cast<int>(dso_type); + } + return nullptr; } -void Dso::FixupSymbolLength() { - Symbol* prev_symbol = nullptr; - for (auto& symbol : symbols_) { - if (prev_symbol != nullptr && prev_symbol->len == 0) { - prev_symbol->len = symbol.addr - prev_symbol->addr; - } - prev_symbol = &symbol; +std::vector<Symbol> DexFileDso::LoadSymbols() { + std::vector<Symbol> symbols; + std::vector<DexFileSymbol> dex_file_symbols; + if (!ReadSymbolsFromDexFile(debug_file_path_, dex_file_offsets_, &dex_file_symbols)) { + android::base::LogSeverity level = symbols_.empty() ? android::base::WARNING + : android::base::DEBUG; + LOG(level) << "Failed to read symbols from " << debug_file_path_; + return symbols; } - if (prev_symbol != nullptr && prev_symbol->len == 0) { - prev_symbol->len = std::numeric_limits<uint64_t>::max() - prev_symbol->addr; + LOG(VERBOSE) << "Read symbols from " << debug_file_path_ << " successfully"; + for (auto& symbol : dex_file_symbols) { + symbols.emplace_back(symbol.name, symbol.offset, symbol.len); } + SortAndFixSymbols(symbols); + return symbols; } const char* DsoTypeToString(DsoType dso_type) { @@ -506,6 +468,8 @@ const char* DsoTypeToString(DsoType dso_type) { return "dso_kernel_module"; case DSO_ELF_FILE: return "dso_elf_file"; + case DSO_DEX_FILE: + return "dso_dex_file"; default: return "unknown"; } diff --git a/simpleperf/dso.h b/simpleperf/dso.h index 4f3df0bd..877e27ff 100644 --- a/simpleperf/dso.h +++ b/simpleperf/dso.h @@ -78,6 +78,7 @@ enum DsoType { DSO_KERNEL, DSO_KERNEL_MODULE, DSO_ELF_FILE, + DSO_DEX_FILE, // For files containing dex files, like .vdex files. }; struct KernelSymbol; @@ -105,7 +106,7 @@ class Dso { static std::unique_ptr<Dso> CreateDso(DsoType dso_type, const std::string& dso_path, bool force_64bit = false); - ~Dso(); + virtual ~Dso(); DsoType type() const { return type_; } @@ -132,19 +133,19 @@ class Dso { uint32_t CreateSymbolDumpId(const Symbol* symbol); // Return the minimum virtual address in program header. - uint64_t MinVirtualAddress(); - void SetMinVirtualAddress(uint64_t min_vaddr) { min_vaddr_ = min_vaddr; } + virtual uint64_t MinVirtualAddress() { return 0; } + virtual void SetMinVirtualAddress(uint64_t) {} const Symbol* FindSymbol(uint64_t vaddr_in_dso); - const std::vector<Symbol>& GetSymbols(); + const std::vector<Symbol>& GetSymbols() { return symbols_; } void SetSymbols(std::vector<Symbol>* symbols); // Create a symbol for a virtual address which can't find a corresponding // symbol in symbol table. void AddUnknownSymbol(uint64_t vaddr_in_dso, const std::string& name); - private: + protected: static bool demangle_; static std::string symfs_dir_; static std::string vmlinux_; @@ -156,15 +157,11 @@ class Dso { static std::string vdso_64bit_; static std::string vdso_32bit_; - Dso(DsoType type, const std::string& path, bool force_64bit); - void Load(); - bool LoadKernel(); - bool LoadKernelModule(); - bool LoadElfFile(); - bool LoadEmbeddedElfFile(); - void FixupSymbolLength(); + Dso(DsoType type, const std::string& path, const std::string& debug_file_path); BuildId GetExpectedBuildId(); - bool CheckReadSymbolResult(ElfStatus result, const std::string& filename); + + void Load(); + virtual std::vector<Symbol> LoadSymbols() = 0; const DsoType type_; // path of the shared library used by the profiled program @@ -174,7 +171,6 @@ class Dso { std::string debug_file_path_; // File name of the shared library, got by removing directories in path_. std::string file_name_; - uint64_t min_vaddr_; std::vector<Symbol> symbols_; // unknown symbols are like [libc.so+0x1234]. std::unordered_map<uint64_t, Symbol> unknown_symbols_; @@ -186,6 +182,27 @@ class Dso { android::base::LogSeverity symbol_warning_loglevel_; }; +class DexFileDso : public Dso { + public: + void AddDexFileOffset(uint64_t dex_file_offset) { + dex_file_offsets_.push_back(dex_file_offset); + } + + const std::vector<uint64_t>& DexFileOffsets() { + return dex_file_offsets_; + } + + protected: + DexFileDso(const std::string& path, const std::string& debug_file_path) + : Dso(DSO_DEX_FILE, path, debug_file_path) {} + + std::vector<Symbol> LoadSymbols() override; + + private: + std::vector<uint64_t> dex_file_offsets_; + friend std::unique_ptr<Dso> Dso::CreateDso(DsoType, const std::string&, bool); +}; + const char* DsoTypeToString(DsoType dso_type); #endif // SIMPLE_PERF_DSO_H_ diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index bd93d8d3..cf9167c2 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -737,7 +737,7 @@ int GetAndroidVersion() { if (!s.empty()) { // Each Android version has a version number: L is 5, M is 6, N is 7, O is 8, etc. if (s[0] >= 'A' && s[0] <= 'Z') { - return s[0] - 'O' + 8; + return s[0] - 'P' + kAndroidVersionP; } if (isdigit(s[0])) { int result; diff --git a/simpleperf/environment.h b/simpleperf/environment.h index 76bdbce7..9f8dc468 100644 --- a/simpleperf/environment.h +++ b/simpleperf/environment.h @@ -120,4 +120,6 @@ bool SignalIsIgnored(int signo); // Return 0 if no android version. int GetAndroidVersion(); +constexpr int kAndroidVersionP = 9; + #endif // SIMPLE_PERF_ENVIRONMENT_H_ diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp index 646de404..29882425 100644 --- a/simpleperf/event_selection_set.cpp +++ b/simpleperf/event_selection_set.cpp @@ -410,6 +410,18 @@ bool EventSelectionSet::NeedKernelSymbol() const { return false; } +void EventSelectionSet::SetRecordNotExecutableMaps(bool record) { + for (auto& group : groups_) { + for (auto& selection : group) { + selection.event_attr.mmap_data = record ? 1 : 0; + } + } +} + +bool EventSelectionSet::RecordNotExecutableMaps() const { + return groups_[0][0].event_attr.mmap_data == 1; +} + static bool CheckIfCpusOnline(const std::vector<int>& cpus) { std::vector<int> online_cpus = GetOnlineCpus(); for (const auto& cpu : cpus) { diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h index 9e2a064c..289208a5 100644 --- a/simpleperf/event_selection_set.h +++ b/simpleperf/event_selection_set.h @@ -103,6 +103,8 @@ class EventSelectionSet { void SetInherit(bool enable); void SetClockId(int clock_id); bool NeedKernelSymbol() const; + void SetRecordNotExecutableMaps(bool record); + bool RecordNotExecutableMaps() const; void AddMonitoredProcesses(const std::set<pid_t>& processes) { processes_.insert(processes.begin(), processes.end()); diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp index db5bed20..c51f2a7f 100644 --- a/simpleperf/nonlinux_support/nonlinux_support.cpp +++ b/simpleperf/nonlinux_support/nonlinux_support.cpp @@ -17,6 +17,7 @@ // Add fake functions to build successfully on darwin. #include <android-base/logging.h> +#include "read_dex_file.h" #include "environment.h" #include "OfflineUnwinder.h" @@ -37,3 +38,8 @@ bool GetKernelBuildId(BuildId*) { bool CanRecordRawData() { return false; } + +bool ReadSymbolsFromDexFile(const std::string&, const std::vector<uint64_t>&, + std::vector<DexFileSymbol>*) { + return true; +} diff --git a/simpleperf/read_dex_file.cpp b/simpleperf/read_dex_file.cpp new file mode 100644 index 00000000..1aa401f2 --- /dev/null +++ b/simpleperf/read_dex_file.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 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 "read_dex_file.h" + +#include <fcntl.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <functional> +#include <string> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/unique_fd.h> + +#include <dex/code_item_accessors-inl.h> +#include <dex/dex_file_loader.h> +#include <dex/dex_file.h> + +static bool OpenDexFiles(const std::string& file_path, std::vector<uint64_t> dex_file_offsets, + const std::function<void (const art::DexFile&, uint64_t)>& callback) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(file_path.c_str(), O_RDONLY | O_CLOEXEC))); + if (fd == -1) { + return false; + } + struct stat buf; + if (fstat(fd, &buf) == -1 || buf.st_size < 0) { + return false; + } + uint64_t file_size = buf.st_size; + void* addr = mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) { + return false; + } + + bool result = true; + for (uint64_t offset : dex_file_offsets) { + if (offset >= file_size || file_size - offset < sizeof(art::DexFile::Header)) { + result = false; + break; + } + auto header = reinterpret_cast<art::DexFile::Header*>(static_cast<char*>(addr) + offset); + if (file_size - offset < header->file_size_) { + result = false; + break; + } + art::DexFileLoader loader; + std::string error; + std::unique_ptr<const art::DexFile> dex_file = loader.Open(reinterpret_cast<uint8_t*>(header), + header->file_size_, "", 0, nullptr, + false, false, &error); + if (!dex_file) { + result = false; + break; + } + callback(*dex_file, offset); + } + munmap(addr, file_size); + return result; +} + +bool ReadSymbolsFromDexFile(const std::string& file_path, + const std::vector<uint64_t>& dex_file_offsets, + std::vector<DexFileSymbol>* symbols) { + auto dexfile_callback = [&](const art::DexFile& dex_file, uint64_t dex_file_offset) { + for (uint32_t i = 0; i < dex_file.NumClassDefs(); ++i) { + const art::DexFile::ClassDef& class_def = dex_file.GetClassDef(i); + const uint8_t* class_data = dex_file.GetClassData(class_def); + if (class_data == nullptr) { + continue; + } + for (art::ClassDataItemIterator it(dex_file, class_data); it.HasNext(); it.Next()) { + if (!it.IsAtMethod()) { + continue; + } + const art::DexFile::CodeItem* code_item = it.GetMethodCodeItem(); + if (code_item == nullptr) { + continue; + } + art::CodeItemInstructionAccessor code(dex_file, code_item); + if (!code.HasCodeItem()) { + continue; + } + symbols->resize(symbols->size() + 1); + DexFileSymbol& symbol = symbols->back(); + symbol.offset = reinterpret_cast<const uint8_t*>(code.Insns()) - dex_file.Begin() + + dex_file_offset; + symbol.len = code.InsnsSizeInCodeUnits() * sizeof(uint16_t); + symbol.name = dex_file.PrettyMethod(it.GetMemberIndex(), false); + } + } + }; + return OpenDexFiles(file_path, dex_file_offsets, dexfile_callback); +} diff --git a/simpleperf/read_dex_file.h b/simpleperf/read_dex_file.h new file mode 100644 index 00000000..cd01f586 --- /dev/null +++ b/simpleperf/read_dex_file.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef SIMPLE_PERF_READ_DEX_FILE_H_ +#define SIMPLE_PERF_READ_DEX_FILE_H_ + +#include <inttypes.h> + +#include <string> +#include <vector> + +struct DexFileSymbol { + uint64_t offset; + uint64_t len; + std::string name; +}; + +bool ReadSymbolsFromDexFile(const std::string& file_path, + const std::vector<uint64_t>& dex_file_offsets, + std::vector<DexFileSymbol>* symbols); + +#endif // SIMPLE_PERF_READ_DEX_FILE_H_ diff --git a/simpleperf/read_dex_file_test.cpp b/simpleperf/read_dex_file_test.cpp new file mode 100644 index 00000000..6b90b8b0 --- /dev/null +++ b/simpleperf/read_dex_file_test.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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 "read_dex_file.h" + +#include <gtest/gtest.h> + +#include <algorithm> + +#include "get_test_data.h" +#include "test_util.h" +#include "utils.h" + +bool operator==(const DexFileSymbol& s1, const DexFileSymbol& s2) { + return s1.offset == s2.offset && s1.len == s2.len && s1.name == s2.name; +} + +TEST(read_dex_file, smoke) { + std::vector<DexFileSymbol> symbols; + ASSERT_TRUE(ReadSymbolsFromDexFile(GetTestData("base.vdex"), {0x28}, &symbols)); + ASSERT_EQ(12435u, symbols.size()); + DexFileSymbol target; + target.offset = 0x6c77e; + target.len = 0x16; + target.name = "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run"; + ASSERT_NE(std::find(symbols.begin(), symbols.end(), target), symbols.end()); +} diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h index 00b8a344..c90b2692 100644 --- a/simpleperf/record_file.h +++ b/simpleperf/record_file.h @@ -55,10 +55,6 @@ class RecordFileWriter { bool WriteCmdlineFeature(const std::vector<std::string>& cmdline); bool WriteBranchStackFeature(); bool WriteFileFeatures(const std::vector<Dso*>& files); - bool WriteFileFeature(const std::string& file_path, - uint32_t file_type, - uint64_t min_vaddr, - const std::vector<const Symbol*>& symbols); bool WriteMetaInfoFeature(const std::unordered_map<std::string, std::string>& info_map); bool WriteFeature(int feature, const std::vector<char>& data); bool EndWriteFeatures(); @@ -76,6 +72,11 @@ class RecordFileWriter { bool Read(void* buf, size_t len); bool GetFilePos(uint64_t* file_pos); bool WriteStringWithLength(const std::string& s); + bool WriteFileFeature(const std::string& file_path, + uint32_t file_type, + uint64_t min_vaddr, + const std::vector<const Symbol*>& symbols, + const std::vector<uint64_t>* dex_file_offsets); bool WriteFeatureBegin(int feature); bool WriteFeatureEnd(int feature); @@ -149,7 +150,7 @@ class RecordFileReader { // information. bool ReadFileFeature(size_t& read_pos, std::string* file_path, uint32_t* file_type, uint64_t* min_vaddr, - std::vector<Symbol>* symbols); + std::vector<Symbol>* symbols, std::vector<uint64_t>* dex_file_offsets); bool ReadMetaInfoFeature(std::unordered_map<std::string, std::string>* info_map); void LoadBuildIdAndFileFeatures(ThreadTree& thread_tree); diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h index 1ddaf002..cd4e9743 100644 --- a/simpleperf/record_file_format.h +++ b/simpleperf/record_file_format.h @@ -47,8 +47,11 @@ file feature section: struct { uint64_t start_vaddr; uint32_t len; - char symbol_name[]; - } symbol_table; + char symbol_name[len+1]; + } symbol_table[symbol_count]; + + uint32_t dex_file_offset_count; // Only when file_type = DSO_DEX_FILE + uint64_t dex_file_offsets[dex_file_offset_count]; // Only when file_type = DSO_DEX_FILE }; meta_info feature section: diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp index 38a4b2d6..9b68c654 100644 --- a/simpleperf/record_file_reader.cpp +++ b/simpleperf/record_file_reader.cpp @@ -443,7 +443,8 @@ bool RecordFileReader::ReadFileFeature(size_t& read_pos, std::string* file_path, uint32_t* file_type, uint64_t* min_vaddr, - std::vector<Symbol>* symbols) { + std::vector<Symbol>* symbols, + std::vector<uint64_t>* dex_file_offsets) { auto it = feature_section_descriptors_.find(FEAT_FILE); if (it == feature_section_descriptors_.end()) { return false; @@ -484,6 +485,13 @@ bool RecordFileReader::ReadFileFeature(size_t& read_pos, p += name.size() + 1; symbols->emplace_back(name, start_vaddr, len); } + dex_file_offsets->clear(); + if (*file_type == static_cast<uint32_t>(DSO_DEX_FILE)) { + uint32_t offset_count; + MoveFromBinaryFormat(offset_count, p); + dex_file_offsets->resize(offset_count); + MoveFromBinaryFormat(dex_file_offsets->data(), offset_count, p); + } CHECK_EQ(size, static_cast<size_t>(p - buf.data())); return true; } @@ -518,10 +526,11 @@ void RecordFileReader::LoadBuildIdAndFileFeatures(ThreadTree& thread_tree) { uint32_t file_type; uint64_t min_vaddr; std::vector<Symbol> symbols; + std::vector<uint64_t> dex_file_offsets; size_t read_pos = 0; while (ReadFileFeature( - read_pos, &file_path, &file_type, &min_vaddr, &symbols)) { - thread_tree.AddDsoInfo(file_path, file_type, min_vaddr, &symbols); + read_pos, &file_path, &file_type, &min_vaddr, &symbols, &dex_file_offsets)) { + thread_tree.AddDsoInfo(file_path, file_type, min_vaddr, &symbols, dex_file_offsets); } } } diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp index fd738d10..c91bd3e7 100644 --- a/simpleperf/record_file_writer.cpp +++ b/simpleperf/record_file_writer.cpp @@ -299,7 +299,8 @@ bool RecordFileWriter::WriteBranchStackFeature() { bool RecordFileWriter::WriteFileFeatures(const std::vector<Dso*>& files) { for (Dso* dso : files) { - if (!dso->HasDumpId()) { + // Always want to dump dex file offsets for DSO_DEX_FILE type. + if (!dso->HasDumpId() && dso->type() != DSO_DEX_FILE) { continue; } uint32_t dso_type = dso->type(); @@ -316,7 +317,11 @@ bool RecordFileWriter::WriteFileFeatures(const std::vector<Dso*>& files) { } std::sort(dump_symbols.begin(), dump_symbols.end(), Symbol::CompareByAddr); - if (!WriteFileFeature(dso->Path(), dso_type, min_vaddr, dump_symbols)) { + const std::vector<uint64_t>* dex_file_offsets = nullptr; + if (dso->type() == DSO_DEX_FILE) { + dex_file_offsets = &static_cast<DexFileDso*>(dso)->DexFileOffsets(); + } + if (!WriteFileFeature(dso->Path(), dso_type, min_vaddr, dump_symbols, dex_file_offsets)) { return false; } } @@ -326,12 +331,16 @@ bool RecordFileWriter::WriteFileFeatures(const std::vector<Dso*>& files) { bool RecordFileWriter::WriteFileFeature(const std::string& file_path, uint32_t file_type, uint64_t min_vaddr, - const std::vector<const Symbol*>& symbols) { + const std::vector<const Symbol*>& symbols, + const std::vector<uint64_t>* dex_file_offsets) { uint32_t size = file_path.size() + 1 + sizeof(uint32_t) * 2 + sizeof(uint64_t) + symbols.size() * (sizeof(uint64_t) + sizeof(uint32_t)); for (const auto& symbol : symbols) { size += strlen(symbol->Name()) + 1; } + if (dex_file_offsets != nullptr) { + size += sizeof(uint32_t) + sizeof(uint64_t) * dex_file_offsets->size(); + } std::vector<char> buf(sizeof(uint32_t) + size); char* p = buf.data(); MoveToBinaryFormat(size, p); @@ -346,6 +355,11 @@ bool RecordFileWriter::WriteFileFeature(const std::string& file_path, MoveToBinaryFormat(len, p); MoveToBinaryFormat(symbol->Name(), strlen(symbol->Name()) + 1, p); } + if (dex_file_offsets != nullptr) { + uint32_t offset_count = dex_file_offsets->size(); + MoveToBinaryFormat(offset_count, p); + MoveToBinaryFormat(dex_file_offsets->data(), offset_count, p); + } CHECK_EQ(buf.size(), static_cast<size_t>(p - buf.data())); return WriteFeature(FEAT_FILE, buf); diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp index 01ba8db1..580338c5 100644 --- a/simpleperf/thread_tree.cpp +++ b/simpleperf/thread_tree.cpp @@ -139,12 +139,15 @@ void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr, thread->maps->version++; } -Dso* ThreadTree::FindUserDsoOrNew(const std::string& filename, uint64_t start_addr) { +Dso* ThreadTree::FindUserDsoOrNew(const std::string& filename, uint64_t start_addr, + DsoType dso_type) { auto it = user_dso_tree_.find(filename); if (it == user_dso_tree_.end()) { bool force_64bit = start_addr > UINT_MAX; - user_dso_tree_[filename] = Dso::CreateDso(DSO_ELF_FILE, filename, force_64bit); - it = user_dso_tree_.find(filename); + std::unique_ptr<Dso> dso = Dso::CreateDso(dso_type, filename, force_64bit); + auto pair = user_dso_tree_.insert(std::make_pair(filename, std::move(dso))); + CHECK(pair.second); + it = pair.first; } return it->second.get(); } @@ -229,6 +232,8 @@ const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip, // Find symbol in user space shared libraries. if (map->flags & map_flags::PROT_JIT_SYMFILE_MAP) { vaddr_in_file = ip; + } else if (dso->type() == DSO_DEX_FILE) { + vaddr_in_file = ip - map->start_addr + map->pgoff; } else { vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress(); } @@ -283,16 +288,29 @@ void ThreadTree::ClearThreadAndMap() { } void ThreadTree::AddDsoInfo(const std::string& file_path, uint32_t file_type, - uint64_t min_vaddr, std::vector<Symbol>* symbols) { + uint64_t min_vaddr, std::vector<Symbol>* symbols, + const std::vector<uint64_t>& dex_file_offsets) { DsoType dso_type = static_cast<DsoType>(file_type); Dso* dso = nullptr; if (dso_type == DSO_KERNEL || dso_type == DSO_KERNEL_MODULE) { dso = FindKernelDsoOrNew(file_path); } else { - dso = FindUserDsoOrNew(file_path); + dso = FindUserDsoOrNew(file_path, 0, dso_type); } dso->SetMinVirtualAddress(min_vaddr); dso->SetSymbols(symbols); + if (!dex_file_offsets.empty()) { + CHECK_EQ(static_cast<int>(dso_type), static_cast<int>(DSO_DEX_FILE)); + for (uint64_t offset : dex_file_offsets) { + static_cast<DexFileDso*>(dso)->AddDexFileOffset(offset); + } + } +} + +void ThreadTree::AddDexFileOffset(const std::string& file_path, uint64_t dex_file_offset) { + Dso* dso = FindUserDsoOrNew(file_path, 0, DSO_DEX_FILE); + CHECK_EQ(static_cast<int>(dso->type()), static_cast<int>(DSO_DEX_FILE)); + static_cast<DexFileDso*>(dso)->AddDexFileOffset(dex_file_offset); } void ThreadTree::Update(const Record& record) { diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h index 11177027..dc936845 100644 --- a/simpleperf/thread_tree.h +++ b/simpleperf/thread_tree.h @@ -124,7 +124,9 @@ class ThreadTree { void ClearThreadAndMap(); void AddDsoInfo(const std::string& file_path, uint32_t file_type, - uint64_t min_vaddr, std::vector<Symbol>* symbols); + uint64_t min_vaddr, std::vector<Symbol>* symbols, + const std::vector<uint64_t>& dex_file_offsets); + void AddDexFileOffset(const std::string& file_path, uint64_t dex_file_offset); // Update thread tree with information provided by record. void Update(const Record& record); @@ -135,7 +137,8 @@ class ThreadTree { private: ThreadEntry* CreateThread(int pid, int tid); Dso* FindKernelDsoOrNew(const std::string& filename); - Dso* FindUserDsoOrNew(const std::string& filename, uint64_t start_addr = 0); + Dso* FindUserDsoOrNew(const std::string& filename, uint64_t start_addr = 0, + DsoType dso_type = DSO_ELF_FILE); MapEntry* AllocateMap(const MapEntry& value); void FixOverlappedMap(MapSet* maps, const MapEntry* map); diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp index 908402b4..9b609616 100644 --- a/simpleperf/tracing.cpp +++ b/simpleperf/tracing.cpp @@ -411,7 +411,7 @@ bool GetTracingData(const std::vector<const EventType*>& event_types, data->clear(); std::vector<TraceType> trace_types; for (const auto& type : event_types) { - CHECK_EQ(PERF_TYPE_TRACEPOINT, type->type); + CHECK_EQ(static_cast<uint32_t>(PERF_TYPE_TRACEPOINT), type->type); size_t pos = type->name.find(':'); TraceType trace_type; trace_type.system = type->name.substr(0, pos); |