diff options
author | Yabin Cui <yabinc@google.com> | 2024-02-22 16:19:35 -0800 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2024-02-27 12:57:10 -0800 |
commit | 57a99140e0d61aa262c95cbe9f0cf15b0ce1e28c (patch) | |
tree | d5c86a2f7cd37ed07d79cb3eb0ec1c6a5dc4f7c0 /simpleperf | |
parent | df6bc1eb89f0f3482cb0a43bc60d23ec6599746f (diff) | |
download | extras-57a99140e0d61aa262c95cbe9f0cf15b0ce1e28c.tar.gz |
simpleperf: refactor CallChainReportBuilder
Decouple CallChainReportBuilder functions into
modifier classes for improved extensibility.
Bug: 325429554
Test: run simpleperf_unit_test
Change-Id: I6a8a5b40f7459597adfeb106e77145983ac2bde1
Diffstat (limited to 'simpleperf')
-rw-r--r-- | simpleperf/report_utils.cpp | 256 | ||||
-rw-r--r-- | simpleperf/report_utils.h | 29 |
2 files changed, 172 insertions, 113 deletions
diff --git a/simpleperf/report_utils.cpp b/simpleperf/report_utils.cpp index ecbc6aa8..5478d3dd 100644 --- a/simpleperf/report_utils.cpp +++ b/simpleperf/report_utils.cpp @@ -189,6 +189,137 @@ static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampolin return false; }; +CallChainReportModifier::~CallChainReportModifier() {} + +// Remove art frames. +class ArtFrameRemover : public CallChainReportModifier { + public: + void Modify(std::vector<CallChainReportEntry>& callchain) override { + auto it = + std::remove_if(callchain.begin(), callchain.end(), [](const CallChainReportEntry& entry) { + return entry.execution_type == CallChainExecutionType::ART_METHOD; + }); + callchain.erase(it, callchain.end()); + } +}; + +// Convert JIT methods to their corresponding interpreted Java methods. +class JITFrameConverter : public CallChainReportModifier { + public: + JITFrameConverter(const ThreadTree& thread_tree) : thread_tree_(thread_tree) {} + + void Modify(std::vector<CallChainReportEntry>& callchain) override { + CollectJavaMethods(); + for (size_t i = 0; i < callchain.size();) { + auto& entry = callchain[i]; + if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) { + // This is a JIT java method, merge it with the interpreted java method having the same + // name if possible. Otherwise, merge it with other JIT java methods having the same name + // by assigning a common dso_name. + if (auto it = java_method_map_.find(std::string(entry.symbol->FunctionName())); + it != java_method_map_.end()) { + entry.dso = it->second.dso; + entry.symbol = it->second.symbol; + // Not enough info to map an offset in a JIT method to an offset in a dex file. So just + // use the symbol_addr. + entry.vaddr_in_file = entry.symbol->addr; + + // ART may call from an interpreted Java method into its corresponding JIT method. To + // avoid showing the method calling itself, remove the JIT frame. + if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso && + callchain[i + 1].symbol == entry.symbol) { + callchain.erase(callchain.begin() + i); + continue; + } + + } else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) { + // Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name. + entry.dso_name = "[JIT cache]"; + } + } + i++; + } + } + + private: + struct JavaMethod { + Dso* dso; + const Symbol* symbol; + JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {} + }; + + void CollectJavaMethods() { + if (!java_method_initialized_) { + java_method_initialized_ = true; + for (Dso* dso : thread_tree_.GetAllDsos()) { + if (dso->type() == DSO_DEX_FILE) { + dso->LoadSymbols(); + for (auto& symbol : dso->GetSymbols()) { + java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol)); + } + } + } + } + } + + const ThreadTree& thread_tree_; + bool java_method_initialized_ = false; + std::unordered_map<std::string, JavaMethod> java_method_map_; +}; + +// Use proguard mapping.txt to de-obfuscate minified symbols. +class JavaMethodDeobfuscater : public CallChainReportModifier { + public: + JavaMethodDeobfuscater(bool remove_r8_synthesized_frame) + : remove_r8_synthesized_frame_(remove_r8_synthesized_frame) {} + + bool AddProguardMappingFile(std::string_view mapping_file) { + return retrace_.AddProguardMappingFile(mapping_file); + } + + void Modify(std::vector<CallChainReportEntry>& callchain) override { + for (size_t i = 0; i < callchain.size();) { + auto& entry = callchain[i]; + if (!IsJavaEntry(entry)) { + i++; + continue; + } + std::string_view name = entry.symbol->FunctionName(); + std::string original_name; + bool synthesized; + if (retrace_.DeObfuscateJavaMethods(name, &original_name, &synthesized)) { + if (synthesized && remove_r8_synthesized_frame_) { + callchain.erase(callchain.begin() + i); + continue; + } + entry.symbol->SetDemangledName(original_name); + } + i++; + } + } + + private: + bool IsJavaEntry(const CallChainReportEntry& entry) { + static const char* COMPILED_JAVA_FILE_SUFFIXES[] = {".odex", ".oat", ".dex"}; + if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD || + entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD) { + return true; + } + if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) { + const std::string& path = entry.dso->Path(); + for (const char* suffix : COMPILED_JAVA_FILE_SUFFIXES) { + if (android::base::EndsWith(path, suffix)) { + return true; + } + } + } + return false; + } + + const bool remove_r8_synthesized_frame_; + ProguardMappingRetrace retrace_; +}; + CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree) : thread_tree_(thread_tree) { const char* env_name = "REMOVE_R8_SYNTHESIZED_FRAME"; @@ -202,13 +333,32 @@ CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree) remove_r8_synthesized_frame_ = true; } } + SetRemoveArtFrame(true); + SetConvertJITFrame(true); +} + +void CallChainReportBuilder::SetRemoveArtFrame(bool enable) { + if (enable) { + art_frame_remover_.reset(new ArtFrameRemover); + } else { + art_frame_remover_.reset(nullptr); + } +} + +void CallChainReportBuilder::SetConvertJITFrame(bool enable) { + if (enable) { + jit_frame_converter_.reset(new JITFrameConverter(thread_tree_)); + } else { + jit_frame_converter_.reset(nullptr); + } } bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) { - if (!retrace_) { - retrace_.reset(new ProguardMappingRetrace); + if (!java_method_deobfuscater_) { + java_method_deobfuscater_.reset(new JavaMethodDeobfuscater(remove_r8_synthesized_frame_)); } - return retrace_->AddProguardMappingFile(mapping_file); + return static_cast<JavaMethodDeobfuscater*>(java_method_deobfuscater_.get()) + ->AddProguardMappingFile(mapping_file); } std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread, @@ -239,17 +389,14 @@ std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntr entry.execution_type = execution_type; } MarkArtFrame(result); - if (remove_art_frame_) { - auto it = std::remove_if(result.begin(), result.end(), [](const CallChainReportEntry& entry) { - return entry.execution_type == CallChainExecutionType::ART_METHOD; - }); - result.erase(it, result.end()); + if (art_frame_remover_) { + art_frame_remover_->Modify(result); } - if (convert_jit_frame_) { - ConvertJITFrame(result); + if (jit_frame_converter_) { + jit_frame_converter_->Modify(result); } - if (retrace_) { - DeObfuscateJavaMethods(result); + if (java_method_deobfuscater_) { + java_method_deobfuscater_->Modify(result); } return result; } @@ -292,91 +439,6 @@ void CallChainReportBuilder::MarkArtFrame(std::vector<CallChainReportEntry>& cal } } -void CallChainReportBuilder::ConvertJITFrame(std::vector<CallChainReportEntry>& callchain) { - CollectJavaMethods(); - for (size_t i = 0; i < callchain.size();) { - auto& entry = callchain[i]; - if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) { - // This is a JIT java method, merge it with the interpreted java method having the same - // name if possible. Otherwise, merge it with other JIT java methods having the same name - // by assigning a common dso_name. - if (auto it = java_method_map_.find(std::string(entry.symbol->FunctionName())); - it != java_method_map_.end()) { - entry.dso = it->second.dso; - entry.symbol = it->second.symbol; - // Not enough info to map an offset in a JIT method to an offset in a dex file. So just - // use the symbol_addr. - entry.vaddr_in_file = entry.symbol->addr; - - // ART may call from an interpreted Java method into its corresponding JIT method. To - // avoid showing the method calling itself, remove the JIT frame. - if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso && - callchain[i + 1].symbol == entry.symbol) { - callchain.erase(callchain.begin() + i); - continue; - } - - } else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) { - // Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name. - entry.dso_name = "[JIT cache]"; - } - } - i++; - } -} - -void CallChainReportBuilder::CollectJavaMethods() { - if (!java_method_initialized_) { - java_method_initialized_ = true; - for (Dso* dso : thread_tree_.GetAllDsos()) { - if (dso->type() == DSO_DEX_FILE) { - dso->LoadSymbols(); - for (auto& symbol : dso->GetSymbols()) { - java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol)); - } - } - } - } -} - -static bool IsJavaEntry(const CallChainReportEntry& entry) { - static const char* COMPILED_JAVA_FILE_SUFFIXES[] = {".odex", ".oat", ".dex"}; - if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD || - entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD) { - return true; - } - if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) { - const std::string& path = entry.dso->Path(); - for (const char* suffix : COMPILED_JAVA_FILE_SUFFIXES) { - if (android::base::EndsWith(path, suffix)) { - return true; - } - } - } - return false; -} - -void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) { - for (size_t i = 0; i < callchain.size();) { - auto& entry = callchain[i]; - if (!IsJavaEntry(entry)) { - i++; - continue; - } - std::string_view name = entry.symbol->FunctionName(); - std::string original_name; - bool synthesized; - if (retrace_->DeObfuscateJavaMethods(name, &original_name, &synthesized)) { - if (synthesized && remove_r8_synthesized_frame_) { - callchain.erase(callchain.begin() + i); - continue; - } - entry.symbol->SetDemangledName(original_name); - } - i++; - } -} - bool ThreadReportBuilder::AggregateThreads(const std::vector<std::string>& thread_name_regex) { size_t i = thread_regs_.size(); thread_regs_.resize(i + thread_name_regex.size()); diff --git a/simpleperf/report_utils.h b/simpleperf/report_utils.h index fa42bba6..78c037df 100644 --- a/simpleperf/report_utils.h +++ b/simpleperf/report_utils.h @@ -91,39 +91,36 @@ struct CallChainReportEntry { CallChainExecutionType execution_type = CallChainExecutionType::NATIVE_METHOD; }; +// a base class for modifying callchain reports +class CallChainReportModifier { + public: + virtual ~CallChainReportModifier(); + + virtual void Modify(std::vector<CallChainReportEntry>& callchain) = 0; +}; + class CallChainReportBuilder { public: CallChainReportBuilder(ThreadTree& thread_tree); // If true, remove interpreter frames both before and after a Java frame. // Default is true. - void SetRemoveArtFrame(bool enable) { remove_art_frame_ = enable; } + void SetRemoveArtFrame(bool enable); // If true, convert a JIT method into its corresponding interpreted Java method. So they can be // merged in reports like flamegraph. Default is true. - void SetConvertJITFrame(bool enable) { convert_jit_frame_ = enable; } + void SetConvertJITFrame(bool enable); // Add proguard mapping.txt to de-obfuscate minified symbols. bool AddProguardMappingFile(std::string_view mapping_file); std::vector<CallChainReportEntry> Build(const ThreadEntry* thread, const std::vector<uint64_t>& ips, size_t kernel_ip_count); private: - struct JavaMethod { - Dso* dso; - const Symbol* symbol; - JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {} - }; - void MarkArtFrame(std::vector<CallChainReportEntry>& callchain); - void ConvertJITFrame(std::vector<CallChainReportEntry>& callchain); - void CollectJavaMethods(); - void DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain); ThreadTree& thread_tree_; - bool remove_art_frame_ = true; bool remove_r8_synthesized_frame_ = false; - bool convert_jit_frame_ = true; - bool java_method_initialized_ = false; - std::unordered_map<std::string, JavaMethod> java_method_map_; - std::unique_ptr<ProguardMappingRetrace> retrace_; + std::unique_ptr<CallChainReportModifier> art_frame_remover_; + std::unique_ptr<CallChainReportModifier> jit_frame_converter_; + std::unique_ptr<CallChainReportModifier> java_method_deobfuscater_; }; struct ThreadReport { |