• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "report_utils.h"
18 
19 #include <android-base/strings.h>
20 
21 #include "JITDebugReader.h"
22 #include "utils.h"
23 
24 namespace simpleperf {
25 
IsArtEntry(const CallChainReportEntry & entry,bool * is_jni_trampoline)26 static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampoline) {
27   if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
28     // art_jni_trampoline/art_quick_generic_jni_trampoline are trampolines used to call jni
29     // methods in art runtime. We want to hide them when hiding art frames.
30     *is_jni_trampoline = android::base::EndsWith(entry.symbol->Name(), "jni_trampoline");
31     return *is_jni_trampoline || android::base::EndsWith(entry.dso->Path(), "/libart.so") ||
32            android::base::EndsWith(entry.dso->Path(), "/libartd.so");
33   }
34   return false;
35 };
36 
AddProguardMappingFile(std::string_view mapping_file)37 bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) {
38   // The mapping file format is described in
39   // https://www.guardsquare.com/en/products/proguard/manual/retrace.
40   LineReader reader(mapping_file);
41   if (!reader.Ok()) {
42     PLOG(ERROR) << "failed to read " << mapping_file;
43     return false;
44   }
45   ProguardMappingClass* cur_class = nullptr;
46   std::string* line;
47   while ((line = reader.ReadLine()) != nullptr) {
48     std::string_view s = *line;
49     if (s.empty() || s[0] == '#') {
50       continue;
51     }
52     auto arrow_pos = s.find(" -> ");
53     if (arrow_pos == s.npos) {
54       continue;
55     }
56     auto arrow_end_pos = arrow_pos + strlen(" -> ");
57 
58     if (s[0] != ' ') {
59       // Match line "original_classname -> obfuscated_classname:".
60       if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) {
61         std::string_view original_classname = s.substr(0, arrow_pos);
62         std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos));
63         cur_class = &proguard_class_map_[obfuscated_classname];
64         cur_class->original_classname = original_classname;
65       }
66     } else if (cur_class != nullptr) {
67       // Match line "... [original_classname.]original_methodname(...)... ->
68       // obfuscated_methodname".
69       if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) {
70         if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) {
71           auto original_methodname = s.substr(space_pos + 1, left_brace_pos - space_pos - 1);
72           if (android::base::StartsWith(original_methodname, cur_class->original_classname)) {
73             original_methodname.remove_prefix(cur_class->original_classname.size() + 1);
74           }
75           std::string obfuscated_methodname(s.substr(arrow_end_pos));
76           cur_class->method_map[obfuscated_methodname] = original_methodname;
77         }
78       }
79     }
80   }
81   return true;
82 }
83 
Build(const ThreadEntry * thread,const std::vector<uint64_t> & ips,size_t kernel_ip_count)84 std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread,
85                                                                 const std::vector<uint64_t>& ips,
86                                                                 size_t kernel_ip_count) {
87   std::vector<CallChainReportEntry> result;
88   result.reserve(ips.size());
89   for (size_t i = 0; i < ips.size(); i++) {
90     const MapEntry* map = thread_tree_.FindMap(thread, ips[i], i < kernel_ip_count);
91     Dso* dso = map->dso;
92     uint64_t vaddr_in_file;
93     const Symbol* symbol = thread_tree_.FindSymbol(map, ips[i], &vaddr_in_file, &dso);
94     CallChainExecutionType execution_type = CallChainExecutionType::NATIVE_METHOD;
95     if (dso->IsForJavaMethod()) {
96       if (dso->type() == DSO_DEX_FILE) {
97         execution_type = CallChainExecutionType::INTERPRETED_JVM_METHOD;
98       } else {
99         execution_type = CallChainExecutionType::JIT_JVM_METHOD;
100       }
101     }
102     result.resize(result.size() + 1);
103     auto& entry = result.back();
104     entry.ip = ips[i];
105     entry.symbol = symbol;
106     entry.dso = dso;
107     entry.vaddr_in_file = vaddr_in_file;
108     entry.map = map;
109     entry.execution_type = execution_type;
110   }
111   MarkArtFrame(result);
112   if (remove_art_frame_) {
113     auto it = std::remove_if(result.begin(), result.end(), [](const CallChainReportEntry& entry) {
114       return entry.execution_type == CallChainExecutionType::ART_METHOD;
115     });
116     result.erase(it, result.end());
117   }
118   if (convert_jit_frame_) {
119     ConvertJITFrame(result);
120   }
121   if (!proguard_class_map_.empty()) {
122     DeObfuscateJavaMethods(result);
123   }
124   return result;
125 }
126 
MarkArtFrame(std::vector<CallChainReportEntry> & callchain)127 void CallChainReportBuilder::MarkArtFrame(std::vector<CallChainReportEntry>& callchain) {
128   // Mark art methods before or after a JVM method.
129   bool near_java_method = false;
130   bool is_jni_trampoline = false;
131   std::vector<size_t> jni_trampoline_positions;
132   for (size_t i = 0; i < callchain.size(); ++i) {
133     auto& entry = callchain[i];
134     if (entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD ||
135         entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) {
136       near_java_method = true;
137 
138       // Mark art frames before this entry.
139       for (int j = static_cast<int>(i) - 1; j >= 0; j--) {
140         if (!IsArtEntry(callchain[j], &is_jni_trampoline)) {
141           break;
142         }
143         callchain[j].execution_type = CallChainExecutionType::ART_METHOD;
144         if (is_jni_trampoline) {
145           jni_trampoline_positions.push_back(j);
146         }
147       }
148     } else if (near_java_method && IsArtEntry(entry, &is_jni_trampoline)) {
149       entry.execution_type = CallChainExecutionType::ART_METHOD;
150       if (is_jni_trampoline) {
151         jni_trampoline_positions.push_back(i);
152       }
153     } else {
154       near_java_method = false;
155     }
156   }
157   // Functions called by art_jni_trampoline are jni methods. And we don't want to hide them.
158   for (auto i : jni_trampoline_positions) {
159     if (i > 0 && callchain[i - 1].execution_type == CallChainExecutionType::ART_METHOD) {
160       callchain[i - 1].execution_type = CallChainExecutionType::NATIVE_METHOD;
161     }
162   }
163 }
164 
ConvertJITFrame(std::vector<CallChainReportEntry> & callchain)165 void CallChainReportBuilder::ConvertJITFrame(std::vector<CallChainReportEntry>& callchain) {
166   CollectJavaMethods();
167   for (size_t i = 0; i < callchain.size();) {
168     auto& entry = callchain[i];
169     if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) {
170       // This is a JIT java method, merge it with the interpreted java method having the same
171       // name if possible. Otherwise, merge it with other JIT java methods having the same name
172       // by assigning a common dso_name.
173       if (auto it = java_method_map_.find(std::string(entry.symbol->FunctionName()));
174           it != java_method_map_.end()) {
175         entry.dso = it->second.dso;
176         entry.symbol = it->second.symbol;
177         // Not enough info to map an offset in a JIT method to an offset in a dex file. So just
178         // use the symbol_addr.
179         entry.vaddr_in_file = entry.symbol->addr;
180 
181         // ART may call from an interpreted Java method into its corresponding JIT method. To
182         // avoid showing the method calling itself, remove the JIT frame.
183         if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso &&
184             callchain[i + 1].symbol == entry.symbol) {
185           callchain.erase(callchain.begin() + i);
186           continue;
187         }
188 
189       } else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) {
190         // Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name.
191         entry.dso_name = "[JIT cache]";
192       }
193     }
194     i++;
195   }
196 }
197 
CollectJavaMethods()198 void CallChainReportBuilder::CollectJavaMethods() {
199   if (!java_method_initialized_) {
200     java_method_initialized_ = true;
201     for (Dso* dso : thread_tree_.GetAllDsos()) {
202       if (dso->type() == DSO_DEX_FILE) {
203         dso->LoadSymbols();
204         for (auto& symbol : dso->GetSymbols()) {
205           java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol));
206         }
207       }
208     }
209   }
210 }
211 
IsJavaEntry(const CallChainReportEntry & entry)212 static bool IsJavaEntry(const CallChainReportEntry& entry) {
213   static const char* COMPILED_JAVA_FILE_SUFFIXES[] = {".odex", ".oat", ".dex"};
214   if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD ||
215       entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD) {
216     return true;
217   }
218   if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
219     const std::string& path = entry.dso->Path();
220     for (const char* suffix : COMPILED_JAVA_FILE_SUFFIXES) {
221       if (android::base::EndsWith(path, suffix)) {
222         return true;
223       }
224     }
225   }
226   return false;
227 }
228 
DeObfuscateJavaMethods(std::vector<CallChainReportEntry> & callchain)229 void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) {
230   for (auto& entry : callchain) {
231     if (!IsJavaEntry(entry)) {
232       continue;
233     }
234     std::string_view name = entry.symbol->FunctionName();
235     if (auto split_pos = name.rfind('.'); split_pos != name.npos) {
236       std::string obfuscated_classname(name.substr(0, split_pos));
237       if (auto it = proguard_class_map_.find(obfuscated_classname);
238           it != proguard_class_map_.end()) {
239         const ProguardMappingClass& proguard_class = it->second;
240         std::string obfuscated_methodname(name.substr(split_pos + 1));
241         if (auto method_it = proguard_class.method_map.find(obfuscated_methodname);
242             method_it != proguard_class.method_map.end()) {
243           std::string new_symbol_name = proguard_class.original_classname + "." + method_it->second;
244           entry.symbol->SetDemangledName(new_symbol_name);
245         }
246       }
247     }
248   }
249 }
250 
251 }  // namespace simpleperf
252