summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--simpleperf/report_utils.cpp222
-rw-r--r--simpleperf/report_utils.h53
-rw-r--r--simpleperf/report_utils_test.cpp70
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)