summaryrefslogtreecommitdiff
path: root/simpleperf
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2021-04-13 13:08:31 -0700
committerYabin Cui <yabinc@google.com>2021-04-14 11:48:19 -0700
commit40eef9e4aed37e4bc097b8dbe806882379e5586d (patch)
treedfd73c7400463b213f40cc471fd9db64c66e8947 /simpleperf
parent6c66522dc41ca30aa4b02be18d955d58b7ee1fc9 (diff)
downloadextras-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.cpp14
-rw-r--r--simpleperf/dso.h1
-rw-r--r--simpleperf/environment.cpp20
-rw-r--r--simpleperf/kallsyms.cpp34
-rw-r--r--simpleperf/report_utils.cpp78
-rw-r--r--simpleperf/report_utils.h14
-rw-r--r--simpleperf/report_utils_test.cpp50
-rw-r--r--simpleperf/utils.cpp9
-rw-r--r--simpleperf/utils.h20
-rw-r--r--simpleperf/utils_test.cpp17
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);
+}