diff options
author | Yabin Cui <yabinc@google.com> | 2021-04-13 13:08:31 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2021-04-14 11:48:19 -0700 |
commit | 40eef9e4aed37e4bc097b8dbe806882379e5586d (patch) | |
tree | dfd73c7400463b213f40cc471fd9db64c66e8947 /simpleperf | |
parent | 6c66522dc41ca30aa4b02be18d955d58b7ee1fc9 (diff) | |
download | extras-40eef9e4aed37e4bc097b8dbe806882379e5586d.tar.gz |
simpleperf: support proguard mapping file in callchain report.
Also refactor LineReader to make it available on windows.
Bug: 169454086
Test: run simpleperf_unit_test
Change-Id: I16844f824b3e6bb52884510fe6e93cf4cf90d4e3
Diffstat (limited to 'simpleperf')
-rw-r--r-- | simpleperf/dso.cpp | 14 | ||||
-rw-r--r-- | simpleperf/dso.h | 1 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 20 | ||||
-rw-r--r-- | simpleperf/kallsyms.cpp | 34 | ||||
-rw-r--r-- | simpleperf/report_utils.cpp | 78 | ||||
-rw-r--r-- | simpleperf/report_utils.h | 14 | ||||
-rw-r--r-- | simpleperf/report_utils_test.cpp | 50 | ||||
-rw-r--r-- | simpleperf/utils.cpp | 9 | ||||
-rw-r--r-- | simpleperf/utils.h | 20 | ||||
-rw-r--r-- | simpleperf/utils_test.cpp | 17 |
10 files changed, 200 insertions, 57 deletions
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp index 9ce3bf19..76751d20 100644 --- a/simpleperf/dso.cpp +++ b/simpleperf/dso.cpp @@ -216,15 +216,19 @@ Symbol::Symbol(std::string_view name, uint64_t addr, uint64_t len) const char* Symbol::DemangledName() const { if (demangled_name_ == nullptr) { const std::string s = Dso::Demangle(name_); - if (s == name_) { - demangled_name_ = name_; - } else { - demangled_name_ = symbol_name_allocator.AllocateString(s); - } + SetDemangledName(s); } return demangled_name_; } +void Symbol::SetDemangledName(std::string_view name) const { + if (name == name_) { + demangled_name_ = name_; + } else { + demangled_name_ = symbol_name_allocator.AllocateString(name); + } +} + static bool CompareSymbolToAddr(const Symbol& s, uint64_t addr) { return s.addr < addr; } diff --git a/simpleperf/dso.h b/simpleperf/dso.h index b2e99df2..4bac3e0d 100644 --- a/simpleperf/dso.h +++ b/simpleperf/dso.h @@ -65,6 +65,7 @@ struct Symbol { const char* Name() const { return name_; } const char* DemangledName() const; + void SetDemangledName(std::string_view name) const; bool HasDumpId() const { return dump_id_ != UINT_MAX; } diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index fff0177d..0365a57a 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -54,16 +54,15 @@ namespace simpleperf { std::vector<int> GetOnlineCpus() { std::vector<int> result; - FILE* fp = fopen("/sys/devices/system/cpu/online", "re"); - if (fp == nullptr) { + LineReader reader("/sys/devices/system/cpu/online"); + if (!reader.Ok()) { PLOG(ERROR) << "can't open online cpu information"; return result; } - LineReader reader(fp); - char* line; + std::string* line; if ((line = reader.ReadLine()) != nullptr) { - if (auto cpus = GetCpusFromString(line); cpus) { + if (auto cpus = GetCpusFromString(*line); cpus) { result.assign(cpus->begin(), cpus->end()); } } @@ -928,17 +927,16 @@ std::optional<std::pair<int, int>> GetKernelVersion() { std::optional<uid_t> GetProcessUid(pid_t pid) { std::string status_file = "/proc/" + std::to_string(pid) + "/status"; - FILE* fp = fopen(status_file.c_str(), "re"); - if (fp == nullptr) { + LineReader reader(status_file); + if (!reader.Ok()) { return std::nullopt; } - LineReader reader(fp); - char* line; + std::string* line; while ((line = reader.ReadLine()) != nullptr) { - if (android::base::StartsWith(line, "Uid:")) { + if (android::base::StartsWith(*line, "Uid:")) { uid_t uid; - if (sscanf(line + strlen("Uid:"), "%u", &uid) == 1) { + if (sscanf(line->data() + strlen("Uid:"), "%u", &uid) == 1) { return uid; } } diff --git a/simpleperf/kallsyms.cpp b/simpleperf/kallsyms.cpp index 8fd3d306..844730f9 100644 --- a/simpleperf/kallsyms.cpp +++ b/simpleperf/kallsyms.cpp @@ -41,20 +41,18 @@ const unsigned int kMinLineTestNonNullSymbols = 10; // Tries to read the kernel symbol file and ensure that at least some symbol // addresses are non-null. bool CanReadKernelSymbolAddresses() { - FILE* fp = fopen(kKallsymsPath, "re"); - if (fp == nullptr) { + LineReader reader(kKallsymsPath); + if (!reader.Ok()) { LOG(DEBUG) << "Failed to read " << kKallsymsPath; return false; } - LineReader reader(fp); auto symbol_callback = [&](const KernelSymbol& symbol) { return (symbol.addr != 0u); }; for (unsigned int i = 0; i < kMinLineTestNonNullSymbols; i++) { - char* line = reader.ReadLine(); + std::string* line = reader.ReadLine(); if (line == nullptr) { return false; } - std::string l = std::string(line); - if (ProcessKernelSymbols(l, symbol_callback)) { + if (ProcessKernelSymbols(*line, symbol_callback)) { return true; } } @@ -161,20 +159,21 @@ std::vector<KernelMmap> GetLoadedModules() { ScopedKptrUnrestrict kptr_unrestrict; if (!kptr_unrestrict.KallsymsAvailable()) return {}; std::vector<KernelMmap> result; - FILE* fp = fopen(kProcModulesPath, "re"); - if (fp == nullptr) { + LineReader reader(kProcModulesPath); + if (!reader.Ok()) { // There is no /proc/modules on Android devices, so we don't print error if failed to open it. PLOG(DEBUG) << "failed to open file /proc/modules"; return result; } - LineReader reader(fp); - char* line; + std::string* line; + std::string name_buf; while ((line = reader.ReadLine()) != nullptr) { // Parse line like: nf_defrag_ipv6 34768 1 nf_conntrack_ipv6, Live 0xffffffffa0fe5000 - char name[reader.MaxLineSize()]; + name_buf.resize(line->size()); + char* name = name_buf.data(); uint64_t addr; uint64_t len; - if (sscanf(line, "%s%" PRIu64 "%*u%*s%*s 0x%" PRIx64, name, &len, &addr) == 3) { + if (sscanf(line->data(), "%s%" PRIu64 "%*u%*s%*s 0x%" PRIx64, name, &len, &addr) == 3) { KernelMmap map; map.name = name; map.start_addr = addr; @@ -198,16 +197,15 @@ std::vector<KernelMmap> GetLoadedModules() { uint64_t GetKernelStartAddress() { ScopedKptrUnrestrict kptr_unrestrict; if (!kptr_unrestrict.KallsymsAvailable()) return 0; - FILE* fp = fopen(kKallsymsPath, "re"); - if (fp == nullptr) { + LineReader reader(kKallsymsPath); + if (!reader.Ok()) { return 0; } - LineReader reader(fp); - char* line; + std::string* line; while ((line = reader.ReadLine()) != nullptr) { - if (strstr(line, "_stext") != nullptr) { + if (strstr(line->data(), "_stext") != nullptr) { uint64_t addr; - if (sscanf(line, "%" PRIx64, &addr) == 1) { + if (sscanf(line->data(), "%" PRIx64, &addr) == 1) { return addr; } } diff --git a/simpleperf/report_utils.cpp b/simpleperf/report_utils.cpp index a4a7cf8d..a9468006 100644 --- a/simpleperf/report_utils.cpp +++ b/simpleperf/report_utils.cpp @@ -19,6 +19,7 @@ #include <android-base/strings.h> #include "JITDebugReader.h" +#include "utils.h" namespace simpleperf { @@ -39,6 +40,53 @@ static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampolin return false; }; +bool CallChainReportBuilder::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()) { + PLOG(ERROR) << "failed to read " << mapping_file; + return false; + } + ProguardMappingClass* cur_class = nullptr; + std::string* line; + while ((line = reader.ReadLine()) != nullptr) { + std::string_view s = *line; + if (s.empty() || s[0] == '#') { + continue; + } + auto arrow_pos = s.find(" -> "); + if (arrow_pos == s.npos) { + continue; + } + auto arrow_end_pos = arrow_pos + strlen(" -> "); + + 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; + } + } + } + } + return true; +} + std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread, const std::vector<uint64_t>& ips, size_t kernel_ip_count) { @@ -76,6 +124,9 @@ std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntr if (convert_jit_frame_) { ConvertJITFrame(result); } + if (!proguard_class_map_.empty()) { + DeObfuscateJavaMethods(result); + } return result; } @@ -132,8 +183,8 @@ void CallChainReportBuilder::ConvertJITFrame(std::vector<CallChainReportEntry>& // 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. + // 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); @@ -163,4 +214,27 @@ void CallChainReportBuilder::CollectJavaMethods() { } } +void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) { + for (auto& entry : callchain) { + if (entry.execution_type != CallChainExecutionType::JIT_JVM_METHOD && + entry.execution_type != CallChainExecutionType::INTERPRETED_JVM_METHOD) { + continue; + } + std::string_view name = entry.symbol->DemangledName(); + 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); + } + } + } + } +} + } // namespace simpleperf diff --git a/simpleperf/report_utils.h b/simpleperf/report_utils.h index 8293f76b..e4cc23d0 100644 --- a/simpleperf/report_utils.h +++ b/simpleperf/report_utils.h @@ -18,6 +18,9 @@ #include <inttypes.h> +#include <string> +#include <string_view> +#include <unordered_map> #include <vector> #include "dso.h" @@ -52,6 +55,8 @@ class CallChainReportBuilder { // 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; } + // 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); @@ -62,15 +67,24 @@ 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(); + void DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain); ThreadTree& thread_tree_; bool remove_art_frame_ = true; 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_; }; } // namespace simpleperf diff --git a/simpleperf/report_utils_test.cpp b/simpleperf/report_utils_test.cpp index 5672c10b..cc246357 100644 --- a/simpleperf/report_utils_test.cpp +++ b/simpleperf/report_utils_test.cpp @@ -63,6 +63,7 @@ class CallChainReportBuilderTest : public testing::Test { file.symbols = { Symbol("java_method1", 0x0, 0x100), Symbol("java_method2", 0x100, 0x100), + Symbol("obfuscated_class.obfuscated_java_method", 0x200, 0x100), }; thread_tree.AddDsoInfo(file); @@ -73,6 +74,7 @@ class CallChainReportBuilderTest : public testing::Test { file.symbols = { Symbol("java_method2", 0x3000, 0x100), Symbol("java_method3", 0x3100, 0x100), + Symbol("obfuscated_class.obfuscated_java_method2", 0x3200, 0x100), }; thread_tree.AddDsoInfo(file); @@ -260,3 +262,51 @@ TEST_F(CallChainReportBuilderTest, keep_art_jni_method) { ASSERT_EQ(entries[1].vaddr_in_file, 0x0); ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD); } + +TEST_F(CallChainReportBuilderTest, add_proguard_mapping_file) { + std::vector<uint64_t> fake_ips = { + 0x2200, // 2200, // obfuscated_class.obfuscated_java_method + 0x3200, // 3200, // obfuscated_class.obfuscated_java_method2 + }; + CallChainReportBuilder builder(thread_tree); + // Symbol names aren't changed when not given proguard mapping files. + std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0); + ASSERT_EQ(entries.size(), 2); + ASSERT_EQ(entries[0].ip, 0x2200); + ASSERT_STREQ(entries[0].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method"); + ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path); + ASSERT_EQ(entries[0].vaddr_in_file, 0x200); + ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD); + ASSERT_EQ(entries[1].ip, 0x3200); + ASSERT_STREQ(entries[1].symbol->DemangledName(), "obfuscated_class.obfuscated_java_method2"); + ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path); + ASSERT_EQ(entries[1].vaddr_in_file, 0x3200); + ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD); + + // Symbol names are changed when given a proguard mapping file. + TemporaryFile tmpfile; + close(tmpfile.release()); + 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" + " 13:13:androidx.core.app.RemoteActionCompat " + "android.support.v4.app.RemoteActionCompatParcelizer.read2(androidx.versionedparcelable." + "VersionedParcel) -> obfuscated_java_method2", + tmpfile.path)); + builder.AddProguardMappingFile(tmpfile.path); + entries = builder.Build(thread, fake_ips, 0); + ASSERT_EQ(entries.size(), 2); + ASSERT_EQ(entries[0].ip, 0x2200); + ASSERT_STREQ(entries[0].symbol->DemangledName(), + "android.support.v4.app.RemoteActionCompatParcelizer.read"); + ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path); + ASSERT_EQ(entries[0].vaddr_in_file, 0x200); + ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD); + ASSERT_EQ(entries[1].ip, 0x3200); + ASSERT_STREQ(entries[1].symbol->DemangledName(), + "android.support.v4.app.RemoteActionCompatParcelizer.read2"); + ASSERT_EQ(entries[1].dso->Path(), fake_jit_cache_path); + ASSERT_EQ(entries[1].vaddr_in_file, 0x3200); + ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD); +} diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp index b89f21a5..069676a6 100644 --- a/simpleperf/utils.cpp +++ b/simpleperf/utils.cpp @@ -71,15 +71,6 @@ const char* OneTimeFreeAllocator::AllocateString(std::string_view s) { return result; } -#if !defined(_WIN32) -char* LineReader::ReadLine() { - if (getline(&buf_, &bufsize_, fp_) != -1) { - return buf_; - } - return nullptr; -} -#endif - android::base::unique_fd FileHelper::OpenReadOnly(const std::string& filename) { int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY | O_BINARY)); return android::base::unique_fd(fd); diff --git a/simpleperf/utils.h b/simpleperf/utils.h index 3104cc2b..ec2303f0 100644 --- a/simpleperf/utils.h +++ b/simpleperf/utils.h @@ -21,6 +21,7 @@ #include <stdio.h> #include <time.h> +#include <fstream> #include <functional> #include <optional> #include <set> @@ -73,20 +74,15 @@ class OneTimeFreeAllocator { class LineReader { public: - explicit LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {} - - ~LineReader() { - free(buf_); - fclose(fp_); - } - - char* ReadLine(); - size_t MaxLineSize() { return bufsize_; } + explicit LineReader(std::string_view file_path) : ifs_(file_path) {} + // Return true if open file successfully. + bool Ok() const { return ifs_.good(); } + // If available, return next line content with new line, otherwise return nullptr. + std::string* ReadLine() { return (std::getline(ifs_, buf_)) ? &buf_ : nullptr; } private: - FILE* fp_; - char* buf_; - size_t bufsize_; + std::ifstream ifs_; + std::string buf_; }; class FileHelper { diff --git a/simpleperf/utils_test.cpp b/simpleperf/utils_test.cpp index 78548622..725ae4db 100644 --- a/simpleperf/utils_test.cpp +++ b/simpleperf/utils_test.cpp @@ -16,6 +16,8 @@ #include <gtest/gtest.h> +#include <android-base/file.h> + #include "get_test_data.h" #include "utils.h" @@ -72,3 +74,18 @@ TEST(utils, GetTidsFromString) { ASSERT_EQ(GetTidsFromString("0,12,9", false), std::make_optional(std::set<pid_t>({0, 9, 12}))); ASSERT_EQ(GetTidsFromString("-2", false), std::nullopt); } + +TEST(utils, LineReader) { + TemporaryFile tmpfile; + close(tmpfile.release()); + ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2", tmpfile.path)); + LineReader reader(tmpfile.path); + ASSERT_TRUE(reader.Ok()); + std::string* line = reader.ReadLine(); + ASSERT_TRUE(line != nullptr); + ASSERT_EQ(*line, "line1"); + line = reader.ReadLine(); + ASSERT_TRUE(line != nullptr); + ASSERT_EQ(*line, "line2"); + ASSERT_TRUE(reader.ReadLine() == nullptr); +} |