diff options
-rw-r--r-- | simpleperf/report_utils.cpp | 222 | ||||
-rw-r--r-- | simpleperf/report_utils.h | 53 | ||||
-rw-r--r-- | simpleperf/report_utils_test.cpp | 70 |
3 files changed, 279 insertions, 66 deletions
diff --git a/simpleperf/report_utils.cpp b/simpleperf/report_utils.cpp index b495ef20..74959374 100644 --- a/simpleperf/report_utils.cpp +++ b/simpleperf/report_utils.cpp @@ -16,6 +16,7 @@ #include "report_utils.h" +#include <android-base/scopeguard.h> #include <android-base/strings.h> #include "JITDebugReader.h" @@ -23,62 +24,173 @@ namespace simpleperf { -static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampoline) { - if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) { - // art_jni_trampoline/art_quick_generic_jni_trampoline are trampolines used to call jni - // methods in art runtime. We want to hide them when hiding art frames. - *is_jni_trampoline = android::base::EndsWith(entry.symbol->Name(), "jni_trampoline"); - return *is_jni_trampoline || android::base::EndsWith(entry.dso->Path(), "/libart.so") || - android::base::EndsWith(entry.dso->Path(), "/libartd.so"); - } - return false; -}; - -bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) { +bool ProguardMappingRetrace::AddProguardMappingFile(std::string_view mapping_file) { // The mapping file format is described in // https://www.guardsquare.com/en/products/proguard/manual/retrace. - LineReader reader(mapping_file); - if (!reader.Ok()) { + // Additional info provided by R8 is described in + // https://r8.googlesource.com/r8/+/refs/heads/main/doc/retrace.md. + line_reader_.reset(new LineReader(mapping_file)); + android::base::ScopeGuard g([&]() { line_reader_ = nullptr; }); + + if (!line_reader_->Ok()) { PLOG(ERROR) << "failed to read " << mapping_file; return false; } - ProguardMappingClass* cur_class = nullptr; + + MoveToNextLine(); + while (cur_line_.type != LineType::LINE_EOF) { + if (cur_line_.type == LineType::CLASS_LINE) { + // Match line "original_classname -> obfuscated_classname:". + std::string_view s = cur_line_.data; + auto arrow_pos = s.find(" -> "); + auto arrow_end_pos = arrow_pos + strlen(" -> "); + if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) { + std::string_view original_classname = s.substr(0, arrow_pos); + std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos)); + MappingClass& cur_class = class_map_[obfuscated_classname]; + cur_class.original_classname = original_classname; + MoveToNextLine(); + if (cur_line_.type == LineType::SYNTHESIZED_COMMENT) { + cur_class.synthesized = true; + MoveToNextLine(); + } + + while (cur_line_.type == LineType::METHOD_LINE) { + ParseMethod(cur_class); + } + continue; + } + } + + // Skip unparsed line. + MoveToNextLine(); + } + return true; +} + +void ProguardMappingRetrace::ParseMethod(MappingClass& mapping_class) { + // Match line "... [original_classname.]original_methodname(...)... -> obfuscated_methodname". + std::string_view s = cur_line_.data; + auto arrow_pos = s.find(" -> "); + auto arrow_end_pos = arrow_pos + strlen(" -> "); + if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) { + if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) { + std::string_view name = s.substr(space_pos + 1, left_brace_pos - space_pos - 1); + bool contains_classname = name.find('.') != name.npos; + if (contains_classname && android::base::StartsWith(name, mapping_class.original_classname)) { + name.remove_prefix(mapping_class.original_classname.size() + 1); + contains_classname = false; + } + std::string original_methodname(name); + std::string obfuscated_methodname(s.substr(arrow_end_pos)); + bool synthesized = false; + + MoveToNextLine(); + if (cur_line_.type == LineType::SYNTHESIZED_COMMENT) { + synthesized = true; + MoveToNextLine(); + } + + auto& method_map = mapping_class.method_map; + if (auto it = method_map.find(obfuscated_methodname); it != method_map.end()) { + // The obfuscated method name already exists. We don't know which one to choose. + // So just prefer the latter one unless it's synthesized. + if (!synthesized) { + it->second.original_name = original_methodname; + it->second.contains_classname = contains_classname; + it->second.synthesized = synthesized; + } + } else { + auto& method = method_map[obfuscated_methodname]; + method.original_name = original_methodname; + method.contains_classname = contains_classname; + method.synthesized = synthesized; + } + return; + } + } + + // Skip unparsed line. + MoveToNextLine(); +} + +void ProguardMappingRetrace::MoveToNextLine() { std::string* line; - while ((line = reader.ReadLine()) != nullptr) { + while ((line = line_reader_->ReadLine()) != nullptr) { std::string_view s = *line; - if (s.empty() || s[0] == '#') { + if (s.empty()) { continue; } - auto arrow_pos = s.find(" -> "); - if (arrow_pos == s.npos) { + size_t non_space_pos = s.find_first_not_of(' '); + if (non_space_pos != s.npos && s[non_space_pos] == '#') { + // Skip all comments unless it's synthesized comment. + if (s.find("com.android.tools.r8.synthesized") != s.npos) { + cur_line_.type = SYNTHESIZED_COMMENT; + cur_line_.data = s; + return; + } + continue; + } + if (s.find(" -> ") == s.npos) { + // Skip unknown lines. continue; } - auto arrow_end_pos = arrow_pos + strlen(" -> "); + cur_line_.data = s; + if (s[0] == ' ') { + cur_line_.type = METHOD_LINE; + } else { + cur_line_.type = CLASS_LINE; + } + return; + } + cur_line_.type = LINE_EOF; +} - if (s[0] != ' ') { - // Match line "original_classname -> obfuscated_classname:". - if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) { - std::string_view original_classname = s.substr(0, arrow_pos); - std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos)); - cur_class = &proguard_class_map_[obfuscated_classname]; - cur_class->original_classname = original_classname; - } - } else if (cur_class != nullptr) { - // Match line "... [original_classname.]original_methodname(...)... -> - // obfuscated_methodname". - if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) { - if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) { - auto original_methodname = s.substr(space_pos + 1, left_brace_pos - space_pos - 1); - if (android::base::StartsWith(original_methodname, cur_class->original_classname)) { - original_methodname.remove_prefix(cur_class->original_classname.size() + 1); - } - std::string obfuscated_methodname(s.substr(arrow_end_pos)); - cur_class->method_map[obfuscated_methodname] = original_methodname; +bool ProguardMappingRetrace::DeObfuscateJavaMethods(std::string_view obfuscated_name, + std::string* original_name, bool* synthesized) { + if (auto split_pos = obfuscated_name.rfind('.'); split_pos != obfuscated_name.npos) { + std::string obfuscated_classname(obfuscated_name.substr(0, split_pos)); + + if (auto it = class_map_.find(obfuscated_classname); it != class_map_.end()) { + const MappingClass& mapping_class = it->second; + const auto& method_map = mapping_class.method_map; + std::string obfuscated_methodname(obfuscated_name.substr(split_pos + 1)); + + if (auto method_it = method_map.find(obfuscated_methodname); method_it != method_map.end()) { + const auto& method = method_it->second; + if (method.contains_classname) { + *original_name = method.original_name; + } else { + *original_name = mapping_class.original_classname + "." + method.original_name; } + *synthesized = method.synthesized; + } else { + // Only the classname is obfuscated. + *original_name = mapping_class.original_classname + "." + obfuscated_methodname; + *synthesized = mapping_class.synthesized; } + return true; } } - return true; + return false; +} + +static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampoline) { + if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) { + // art_jni_trampoline/art_quick_generic_jni_trampoline are trampolines used to call jni + // methods in art runtime. We want to hide them when hiding art frames. + *is_jni_trampoline = android::base::EndsWith(entry.symbol->Name(), "jni_trampoline"); + return *is_jni_trampoline || android::base::EndsWith(entry.dso->Path(), "/libart.so") || + android::base::EndsWith(entry.dso->Path(), "/libartd.so"); + } + return false; +}; + +bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) { + if (!retrace_) { + retrace_.reset(new ProguardMappingRetrace); + } + return retrace_->AddProguardMappingFile(mapping_file); } std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread, @@ -118,7 +230,7 @@ std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntr if (convert_jit_frame_) { ConvertJITFrame(result); } - if (!proguard_class_map_.empty()) { + if (retrace_) { DeObfuscateJavaMethods(result); } return result; @@ -227,29 +339,23 @@ static bool IsJavaEntry(const CallChainReportEntry& entry) { } void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) { - for (auto& entry : 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(); - if (auto split_pos = name.rfind('.'); split_pos != name.npos) { - std::string obfuscated_classname(name.substr(0, split_pos)); - if (auto it = proguard_class_map_.find(obfuscated_classname); - it != proguard_class_map_.end()) { - const ProguardMappingClass& proguard_class = it->second; - std::string obfuscated_methodname(name.substr(split_pos + 1)); - if (auto method_it = proguard_class.method_map.find(obfuscated_methodname); - method_it != proguard_class.method_map.end()) { - std::string new_symbol_name = proguard_class.original_classname + "." + method_it->second; - entry.symbol->SetDemangledName(new_symbol_name); - } else { - // Only the classname is obfuscated. - std::string new_symbol_name = - proguard_class.original_classname + "." + obfuscated_methodname; - entry.symbol->SetDemangledName(new_symbol_name); - } + std::string original_name; + bool synthesized; + if (retrace_->DeObfuscateJavaMethods(name, &original_name, &synthesized)) { + if (synthesized) { + callchain.erase(callchain.begin() + i); + continue; } + entry.symbol->SetDemangledName(original_name); } + i++; } } diff --git a/simpleperf/report_utils.h b/simpleperf/report_utils.h index d277db74..dbf95270 100644 --- a/simpleperf/report_utils.h +++ b/simpleperf/report_utils.h @@ -26,9 +26,53 @@ #include "RegEx.h" #include "dso.h" #include "thread_tree.h" +#include "utils.h" namespace simpleperf { +class ProguardMappingRetrace { + public: + // Add proguard mapping.txt to de-obfuscate minified symbols. + bool AddProguardMappingFile(std::string_view mapping_file); + + bool DeObfuscateJavaMethods(std::string_view obfuscated_name, std::string* original_name, + bool* synthesized); + + private: + struct MappingMethod { + std::string original_name; + bool contains_classname; + bool synthesized; + }; + + struct MappingClass { + std::string original_classname; + bool synthesized = false; + // Map from obfuscated method names to MappingMethod. + std::unordered_map<std::string, MappingMethod> method_map; + }; + + enum LineType { + SYNTHESIZED_COMMENT, + CLASS_LINE, + METHOD_LINE, + LINE_EOF, + }; + + struct LineInfo { + LineType type; + std::string_view data; + }; + + void ParseMethod(MappingClass& mapping_class); + void MoveToNextLine(); + + // Map from obfuscated class names to ProguardMappingClass. + std::unordered_map<std::string, MappingClass> class_map_; + std::unique_ptr<LineReader> line_reader_; + LineInfo cur_line_; +}; + enum class CallChainExecutionType { NATIVE_METHOD, INTERPRETED_JVM_METHOD, @@ -68,12 +112,6 @@ class CallChainReportBuilder { JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {} }; - struct ProguardMappingClass { - std::string original_classname; - // Map from minified method names to original method names. - std::unordered_map<std::string, std::string> method_map; - }; - void MarkArtFrame(std::vector<CallChainReportEntry>& callchain); void ConvertJITFrame(std::vector<CallChainReportEntry>& callchain); void CollectJavaMethods(); @@ -84,8 +122,7 @@ class CallChainReportBuilder { bool convert_jit_frame_ = true; bool java_method_initialized_ = false; std::unordered_map<std::string, JavaMethod> java_method_map_; - // Map from minified class names to ProguardMappingClass. - std::unordered_map<std::string, ProguardMappingClass> proguard_class_map_; + std::unique_ptr<ProguardMappingRetrace> retrace_; }; struct ThreadReport { diff --git a/simpleperf/report_utils_test.cpp b/simpleperf/report_utils_test.cpp index 6b7d8670..7b393fb5 100644 --- a/simpleperf/report_utils_test.cpp +++ b/simpleperf/report_utils_test.cpp @@ -25,6 +25,47 @@ using namespace simpleperf; +TEST(ProguardMappingRetrace, smoke) { + TemporaryFile tmpfile; + close(tmpfile.release()); + ASSERT_TRUE( + android::base::WriteStringToFile("original.class.A -> A:\n" + "\n" + " void method_a() -> a\n" + " void method_b() -> b\n" + " # {\"id\":\"com.android.tools.r8.synthesized\"}\n" + " # some other comments\n" + " void original.class.M.method_c() -> c\n" + " void original.class.A.method_d() -> d\n" + "original.class.B -> B:\n" + "# some other comments\n" + "original.class.C -> C:\n" + "# {\'id\':\'com.android.tools.r8.synthesized\'}\n", + tmpfile.path)); + ProguardMappingRetrace retrace; + ASSERT_TRUE(retrace.AddProguardMappingFile(tmpfile.path)); + std::string original_name; + bool synthesized; + ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.a", &original_name, &synthesized)); + ASSERT_EQ(original_name, "original.class.A.method_a"); + ASSERT_FALSE(synthesized); + ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.b", &original_name, &synthesized)); + ASSERT_EQ(original_name, "original.class.A.method_b"); + ASSERT_TRUE(synthesized); + ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.c", &original_name, &synthesized)); + ASSERT_EQ(original_name, "original.class.M.method_c"); + ASSERT_FALSE(synthesized); + ASSERT_TRUE(retrace.DeObfuscateJavaMethods("A.d", &original_name, &synthesized)); + ASSERT_EQ(original_name, "original.class.A.method_d"); + ASSERT_FALSE(synthesized); + ASSERT_TRUE(retrace.DeObfuscateJavaMethods("B.b", &original_name, &synthesized)); + ASSERT_EQ(original_name, "original.class.B.b"); + ASSERT_FALSE(synthesized); + ASSERT_TRUE(retrace.DeObfuscateJavaMethods("C.c", &original_name, &synthesized)); + ASSERT_EQ(original_name, "original.class.C.c"); + ASSERT_TRUE(synthesized); +} + class CallChainReportBuilderTest : public testing::Test { protected: virtual void SetUp() { @@ -321,6 +362,35 @@ TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) { ASSERT_EQ(entries[2].execution_type, CallChainExecutionType::JIT_JVM_METHOD); } +TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file_synthesized_frame) { + std::vector<uint64_t> fake_ips = { + 0x2200, // 2200, // obfuscated_class.obfuscated_java_method + 0x3200, // 3200, // obfuscated_class.obfuscated_java_method2 + }; + + // Synthesized frames are removed. + TemporaryFile tmpfile; + ASSERT_TRUE(android::base::WriteStringToFile( + "android.support.v4.app.RemoteActionCompatParcelizer -> obfuscated_class:\n" + " 13:13:androidx.core.app.RemoteActionCompat read(androidx.versionedparcelable.Versioned" + "Parcel) -> obfuscated_java_method\n" + " # {\"id\":\"com.android.tools.r8.synthesized\"}\n" + " 13:13:androidx.core.app.RemoteActionCompat " + "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable." + "VersionedParcel) -> obfuscated_java_method2", + tmpfile.path)); + CallChainReportBuilder builder(thread_tree); + builder.AddProguardMappingFile(tmpfile.path); + std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0); + ASSERT_EQ(entries.size(), 1); + ASSERT_EQ(entries[0].ip, 0x3200); + ASSERT_STREQ(entries[0].symbol->DemangledName(), + "android.support.v4.app.RemoteActionCompatParcelizer.read2"); + ASSERT_EQ(entries[0].dso->Path(), fake_jit_cache_path); + ASSERT_EQ(entries[0].vaddr_in_file, 0x3200); + ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::JIT_JVM_METHOD); +} + TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file_for_jit_method_with_signature) { std::vector<uint64_t> fake_ips = { 0x3200, // 3200, // void ctep.v(cteo, ctgc, ctbn) |